From 1ee45344820c630937289d2d50da8c9dca32eead Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 06:02:39 -0500 Subject: [PATCH 01/12] chore: add codex project metadata --- .agents/skills/build-cli/SKILL.md | 45 ++++ .agents/skills/github-pr-review-fix/SKILL.md | 212 +++++++++++++++++++ .codex/migrate-to-codex-report.txt | 17 ++ .gitignore | 1 + AGENTS.md | 22 ++ 5 files changed, 297 insertions(+) create mode 100644 .agents/skills/build-cli/SKILL.md create mode 100644 .agents/skills/github-pr-review-fix/SKILL.md create mode 100644 .codex/migrate-to-codex-report.txt create mode 100644 AGENTS.md diff --git a/.agents/skills/build-cli/SKILL.md b/.agents/skills/build-cli/SKILL.md new file mode 100644 index 000000000..ac8dd290b --- /dev/null +++ b/.agents/skills/build-cli/SKILL.md @@ -0,0 +1,45 @@ +--- +name: "build-cli" +description: "Build the unity-mcp-cli TypeScript CLI tool and link it globally for terminal use." +--- + +# Build CLI + +Build the `unity-mcp-cli` TypeScript project and optionally link it globally. +If the user includes `--link` or explicitly asks for a global link, run the optional link step. + +## Step 1 — Install Dependencies + +```bash +cd cli && npm install +``` + +Only needed if `node_modules/` is missing or `package.json` changed. + +## Step 2 — Build TypeScript + +```bash +cd cli && npm run build +``` + +This compiles `src/**/*.ts` → `dist/**/*.js` via `tsc`. + +## Step 3 — Link Globally (only when requested or for first-time setup) + +```bash +cd cli && npm link +``` + +This creates a global symlink so `unity-mcp-cli` is available from any terminal. Only needed once — after that, `npm run build` alone is sufficient. + +## Step 4 — Verify + +```bash +unity-mcp-cli --version +``` + +Confirm the CLI is accessible and shows the expected version. + +## Report + +Print the version and confirm success. If any step failed, show the error output. diff --git a/.agents/skills/github-pr-review-fix/SKILL.md b/.agents/skills/github-pr-review-fix/SKILL.md new file mode 100644 index 000000000..36b0b351e --- /dev/null +++ b/.agents/skills/github-pr-review-fix/SKILL.md @@ -0,0 +1,212 @@ +--- +name: "github-pr-review-fix" +description: "Review and resolve PR comments from GitHub. Validates each comment, fixes legitimate issues." +--- + +# Review PR Comments + +Review unresolved comments on the GitHub pull request associated with the current branch. Validate each comment, then fix legitimate issues. +If the user includes a PR number, use that PR instead of inferring one from the current branch. + +## Step 1 — Identify the Pull Request + +1. If the user supplied a PR number, use it. +2. Otherwise, detect the current git branch and find its open PR: + ```bash + gh pr view --json number,url,headRefName,baseRefName + ``` +3. If no PR is found, stop and tell the user. + +## Step 2 — Fetch All Unresolved Review Comments + +Fetch PR review comments (not issue-level comments) using the GitHub CLI: + +```bash +gh api repos/{owner}/{repo}/pulls/{number}/comments --paginate +``` + +Filter to comments where the thread is **not resolved**. Group comments by thread (same `in_reply_to_id` or same `path` + `line`/`original_line`). For each thread, treat the **first comment** as the review request and subsequent comments as discussion. + +Also fetch general PR comments (issue-level): +```bash +gh api repos/{owner}/{repo}/issues/{number}/comments --paginate +``` + +If there are zero unresolved comments, report that and stop. + +## Step 3 — Validate and Fix Comments in Parallel (Sub-agents) + +For **each** unresolved comment or comment thread, spawn a **sub-agent** in parallel. If the runtime allows explicit model selection, prefer a smaller cost-efficient coding model. Each agent validates the comment and, if legitimate, fixes it immediately in place. + +**Conflict avoidance**: If multiple comments target the **same file**, run those agents **sequentially** (not in parallel) to avoid edit conflicts. Comments on **different files** run in parallel. + +### Sub-agent Prompt Template + +``` +You are a code review comment validator and fixer. Your job is to determine whether a PR review comment identifies a real issue, and if so, fix it immediately. + +## Comment Details +- **Author**: {comment_author} +- **File**: {file_path} +- **Line(s)**: {line_range} +- **Comment**: {comment_body} +- **Thread context** (if any): {thread_replies} +- **Comment ID**: {comment_id} + +## Phase 1 — Validate + +1. Read the file referenced in the comment. Focus on the specific lines mentioned. +2. Read surrounding context (50 lines above and below) to understand the code fully. +3. Analyze whether the comment identifies a legitimate issue: + - Is the described problem actually present in the code? + - Is the suggestion an improvement or just a style preference? + - Does the comment apply to the current state of the code (it may already be fixed)? + - Is this a nitpick / optional suggestion vs. a real bug, logic error, or missing handling? + +4. Decide: FIX or IGNORE. + - If IGNORE, skip to the report below. + - If FIX, proceed to Phase 2. + +## Phase 2 — Fix (only if verdict is FIX) + +1. Apply the minimal fix that addresses the comment. Do not refactor unrelated code. +2. Ensure the fix: + - Does not break surrounding logic + - Follows the existing code style and conventions + - Preserves all existing functionality +3. If the fix requires changes in multiple locations within the same file, make all changes. +4. If you are unsure whether a fix is safe, do NOT apply it — set verdict to NEEDS_MANUAL_REVIEW instead. + +HARD CONSTRAINTS: +- Only modify the file(s) specified. Do not touch other files. +- Do not add comments explaining the fix in the code. +- Do not refactor or "improve" code beyond what the comment asks for. +- Keep changes minimal and surgical. + +## Report + +Return your result in this exact format: + +VERDICT: FIX | IGNORE | NEEDS_MANUAL_REVIEW +CONFIDENCE: HIGH | MEDIUM | LOW +REASON: <1-2 sentence explanation> +SUMMARY: <1 sentence describing what was changed, only if VERDICT is FIX> +FILE: {file_path} +LINES: {line_range} +COMMENT_ID: {comment_id} +FIXED: YES | NO +``` + +## Step 4 — Resolve Fixed Comments on GitHub + +After a fix sub-agent successfully fixes an issue, **resolve the corresponding review thread on GitHub** using the GraphQL API. + +1. First, find the thread ID for the comment. Use the `node_id` from the comment (fetched in Step 2) to query the thread: + ```bash + gh api graphql -f query=' + query { + node(id: "{comment_node_id}") { + ... on PullRequestReviewComment { + pullRequestReview { + id + } + id + isMinimized + } + } + } + ' + ``` + +2. Resolve the review thread using the `resolveReviewThread` mutation. The thread ID can be obtained from the pull request's review threads: + ```bash + gh api graphql -f query=' + query { + repository(owner: "{owner}", name: "{repo}") { + pullRequest(number: {number}) { + reviewThreads(first: 100) { + nodes { + id + isResolved + comments(first: 1) { + nodes { + id + body + path + line + } + } + } + } + } + } + } + ' + ``` + Match the thread by comparing `path` and `line` (or `body`) to the fixed comment, then resolve it: + ```bash + gh api graphql -f query=' + mutation { + resolveReviewThread(input: {threadId: "{thread_id}"}) { + thread { + id + isResolved + } + } + } + ' + ``` + +3. For every successfully fixed comment, reply to the thread before resolving it: + ```bash + gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies \ + -f body="## Agentic reply (github-pr-review-fix) + +Fixed. {summary} + +_If you disagree with the fix, please reopen this thread._" + ``` + Then resolve the thread. If the resolve call fails, note it in the report but do not block on it. + +4. For **IGNORE** verdicts, reply to the comment thread with an explanation of why it was ignored, then resolve the thread: + ```bash + gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies \ + -f body="## Agentic reply (github-pr-review-fix) + +This comment was reviewed and determined to not require a code change. + +**Reason**: {reason} + +_If you disagree, please reopen this thread._" + ``` + Then resolve the thread using the same `resolveReviewThread` mutation as above. + +5. For **NEEDS_MANUAL_REVIEW** verdicts, reply to the thread but do **not** resolve it: + ```bash + gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies \ + -f body="## Agentic reply (github-pr-review-fix) + +This comment requires manual review — the agent could not safely apply an automated fix. + +**Reason**: {reason} + +_Leaving this thread unresolved for human attention._" + ``` + +## Step 5 — Report Results + +After all agents complete, provide a summary table: + +``` +| # | File | Comment | Verdict | Reason | Resolved | +|---|------|---------|---------|--------|----------| +| 1 | path/to/file.cs:42 | "Missing null check" | FIX | Added null guard | Yes | +| 2 | path/to/other.cs:10 | "Consider renaming" | IGNORE | Style preference, no bug | Yes (replied) | +| 3 | path/to/util.cs:77 | "Race condition" | NEEDS_MANUAL_REVIEW | Unsafe to auto-fix, needs human judgment | — | +``` + +For **IGNORE** verdicts, the reason is posted as a reply in the thread and the thread is resolved. The user can reopen if they disagree. + +For **NEEDS_MANUAL_REVIEW** verdicts, explain why the agent couldn't safely apply a fix. These threads are left **unresolved** for human attention. + +End with a count: `X comments fixed and resolved, Y ignored and resolved, Z need manual review.` diff --git a/.codex/migrate-to-codex-report.txt b/.codex/migrate-to-codex-report.txt new file mode 100644 index 000000000..3d366b4aa --- /dev/null +++ b/.codex/migrate-to-codex-report.txt @@ -0,0 +1,17 @@ +Migration inventory: + inactive: instruction files - none found + active: skills - 2 found + - build-cli + - github-pr-review-fix + inactive: command sources - none found + inactive: subagents - none found +Migration surfaces: + inactive: AGENTS.md - No supported instruction file found. + active: skills - 2 skill(s) converted. + inactive: MCP config - No settings or MCP config found. + inactive: subagents - No subagents found. +Migration report: + manual_fix_required: .agents/skills/build-cli/SKILL.md - Manual review required for Claude skill fields: `argument-hint`, `disable-model-invocation`. + manual_fix_required: .agents/skills/github-pr-review-fix/SKILL.md - Manual review required for Claude skill fields: `argument-hint`, `disable-model-invocation`. + overwritten: /Users/suporte/Unity-MCP/.agents/skills/build-cli - Existing Codex skill will be replaced. + overwritten: /Users/suporte/Unity-MCP/.agents/skills/github-pr-review-fix - Existing Codex skill will be replaced. diff --git a/.gitignore b/.gitignore index eaa326f6d..bfa265c66 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .secrets .unity-license *.ulf +.DS_Store # AI configurations **/.claude/settings.local.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..424abd26b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,22 @@ +# AGENTS.md + +## What This Is + +Unity-MCP bridges LLMs (Codex, Cursor, Copilot, etc.) with Unity Editor and Runtime via the [Model Context Protocol](https://modelcontextprotocol.io/). Sub-projects: `Unity-MCP-Server/` (ASP.NET Core + MCP SDK), `Unity-MCP-Plugin/` (Unity Editor/Runtime plugin), `cli/`, `Installer/`, `Unity-Tests/`. + +## Build / Run + +- Bump version: `.\commands\bump-version.ps1 ` +- CI/CD pipelines live in `.github/workflows/`. + +## Project Constitution + +Non-negotiable principles and architecture constraints: [`.specify/memory/constitution.md`](.specify/memory/constitution.md). You MUST read the constitution before performing any code review. + +## Find Detail In + +- [docs/Codex/architecture.md](docs/Codex/architecture.md) — System architecture: SignalR bridge, main-thread execution, deterministic port hashing +- [docs/Codex/style.md](docs/Codex/style.md) — Coding conventions: `#nullable enable`, no reflection for private access, namespace pattern, copyright headers +- [docs/Codex/release.md](docs/Codex/release.md) — Release, versioning, CI/CD +- [docs/Codex/documentation-sync.md](docs/Codex/documentation-sync.md) — README translation/copy sync requirements +- `Unity-MCP-Server/AGENTS.md`, `Unity-MCP-Plugin/AGENTS.md` — sub-project specifics From e7bc0b2e90768a205e75816c49de70944c81c351 Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 06:03:23 -0500 Subject: [PATCH 02/12] feat: add vscode extension status scaffold --- docs/dev/issue-613-vscode-extension-plan.md | 212 +++ vscode-extension/.gitignore | 4 + vscode-extension/.vscode/launch.json | 17 + vscode-extension/.vscode/tasks.json | 17 + vscode-extension/README.md | 45 + vscode-extension/package-lock.json | 1630 +++++++++++++++++++ vscode-extension/package.json | 74 + vscode-extension/src/extension.ts | 111 ++ vscode-extension/src/logging.ts | 101 ++ vscode-extension/src/projectStatus.test.ts | 68 + vscode-extension/src/projectStatus.ts | 178 ++ vscode-extension/src/workspace.ts | 32 + vscode-extension/tsconfig.json | 26 + 13 files changed, 2515 insertions(+) create mode 100644 docs/dev/issue-613-vscode-extension-plan.md create mode 100644 vscode-extension/.gitignore create mode 100644 vscode-extension/.vscode/launch.json create mode 100644 vscode-extension/.vscode/tasks.json create mode 100644 vscode-extension/README.md create mode 100644 vscode-extension/package-lock.json create mode 100644 vscode-extension/package.json create mode 100644 vscode-extension/src/extension.ts create mode 100644 vscode-extension/src/logging.ts create mode 100644 vscode-extension/src/projectStatus.test.ts create mode 100644 vscode-extension/src/projectStatus.ts create mode 100644 vscode-extension/src/workspace.ts create mode 100644 vscode-extension/tsconfig.json diff --git a/docs/dev/issue-613-vscode-extension-plan.md b/docs/dev/issue-613-vscode-extension-plan.md new file mode 100644 index 000000000..8a7bbaf70 --- /dev/null +++ b/docs/dev/issue-613-vscode-extension-plan.md @@ -0,0 +1,212 @@ +# Issue 613 Plan: VS Code Extension Marketplace + +## Goal + +Create a VS Code extension package for Unity MCP that is safe to install from the VS Code Extensions Marketplace, reuses existing repo logic where practical, and reduces the current manual `.vscode/mcp.json` setup burden without widening the Unity MCP trust surface unnecessarily. + +## Current Findings + +- Issue [`#613`](https://github.com/IvanMurzak/Unity-MCP/issues/613) is minimal and open. Its only explicit acceptance signal today is: make a VS Code extension users can install from the marketplace. +- The repo is Apache-2.0 licensed. No `CONTRIBUTING*`, CLA, or DCO files were found in the repository root. +- The CLI already exposes a side-effect-free library API in [cli/src/lib.ts](/Users/suporte/Unity-MCP/cli/src/lib.ts) and documents it in [cli/README.md](/Users/suporte/Unity-MCP/cli/README.md). +- `setupMcp()` already knows how to generate VS Code MCP config for agent id `vscode-copilot` and targets `.vscode/mcp.json` via [cli/src/utils/agents.ts](/Users/suporte/Unity-MCP/cli/src/utils/agents.ts) and [cli/src/lib/setup-mcp.ts](/Users/suporte/Unity-MCP/cli/src/lib/setup-mcp.ts). +- The Unity-side VS Code configurator in [VisualStudioCodeCopilotConfigurator.cs](/Users/suporte/Unity-MCP/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Editor/Scripts/UI/AiAgentConfigurators/Impl/VisualStudioCodeCopilotConfigurator.cs) still tells users to create `.vscode/mcp.json` manually and start the `ai-game-developer` server manually after each VS Code restart. +- Current VS Code docs show: + - MCP servers are managed through `mcp.json`, the Extensions view, and inline code lenses. + - VS Code has a current MCP developer guide and workspace trust guidance. + - Workspace Trust should be treated explicitly for extensions that touch local projects or execute tools. + +## Working Decisions + +- Use a new isolated package folder: `vscode-extension/`. +- Reuse `unity-mcp-cli` library functions where possible. Avoid shelling out by default. +- Keep v1 local-first. No telemetry. No publishing flow changes in this repo yet. +- Treat write actions and Unity launch actions as trust-sensitive and opt-in only. +- Start with read-only diagnostics before adding project mutation commands. + +## Slice Plan + +### Slice 1: Extension scaffold and read-only status + +Scope: +- Add a standalone VS Code extension package. +- Add safe activation, output logging, Workspace Trust awareness, Unity project detection, plugin detection, and MCP config detection. +- Add `Unity MCP: Check Status` and `Unity MCP: Show Output`. + +Why first: +- Gives a testable extension immediately. +- Builds the logging and trust baseline needed for later write flows. +- Avoids any project mutation while we confirm extension packaging and UX. + +Automated tests: +- Unit test Unity project detection. +- Unit test plugin detection from `Packages/manifest.json`. +- Unit test `.vscode/mcp.json` parsing and warning paths. + +Debug logging to include: +- `activate:start` +- `activate:complete` +- `workspace:pick` +- `status:computeStart` +- `status:computeResult` +- `status:error` +- `trust:granted` + +Manual validation: +1. Install extension package dependencies. +2. Build the extension. +3. Launch the Extension Development Host from VS Code. +4. Open a non-Unity folder and run `Unity MCP: Check Status`. +5. Open a Unity project and run `Unity MCP: Check Status`. +6. Confirm the `Unity MCP` output channel reports trust state, Unity detection, plugin status, and MCP config status. + +### Slice 2: Shared CLI adapter for setup flows + +Scope: +- Add a small internal adapter that imports typed functions from `unity-mcp-cli`. +- Prove runtime compatibility inside the VS Code extension host. +- Avoid any duplicated MCP config generation logic. + +Automated tests: +- Adapter success/failure mapping. +- Progress event forwarding. +- Config generation parity check for `vscode-copilot`. + +Debug logging to include: +- `cliAdapter:loadStart` +- `cliAdapter:loadSuccess` +- `cliAdapter:loadFailure` +- `cliAdapter:callStart` +- `cliAdapter:callSuccess` +- `cliAdapter:callFailure` + +Manual validation: +1. Run a dry adapter command from the extension without mutating files. +2. Confirm output channel logs progress events. + +### Slice 3: Configure Project command + +Scope: +- Add `Unity MCP: Configure Project`. +- Generate or update `.vscode/mcp.json` through shared library logic. +- Require trusted workspace and explicit user action. + +Automated tests: +- Creates `.vscode/mcp.json` when missing. +- Preserves unrelated config content where appropriate. +- Re-running is idempotent. +- Errors are surfaced clearly when the workspace is not a Unity project. + +Debug logging to include: +- `configure:start` +- `configure:precheck` +- `configure:writeSuccess` +- `configure:writeFailure` + +Manual validation: +1. Trust the workspace. +2. Run `Unity MCP: Configure Project`. +3. Inspect `.vscode/mcp.json`. +4. Open VS Code MCP server UI and confirm the server entry appears. + +### Slice 4: Install Plugin command + +Scope: +- Add `Unity MCP: Install Plugin` if `installPlugin()` reuse is clean and safe. +- Keep user confirmation explicit. +- Never silently install anything. + +Automated tests: +- Plugin install happy path. +- Already-installed path. +- Failure path for missing or invalid Unity project. + +Debug logging to include: +- `pluginInstall:start` +- `pluginInstall:precheck` +- `pluginInstall:result` +- `pluginInstall:error` + +Manual validation: +1. Use a Unity project without the package installed. +2. Run `Unity MCP: Install Plugin`. +3. Confirm `Packages/manifest.json` is updated as expected. +4. Open Unity and confirm package import begins. + +### Slice 5: Optional convenience actions + +Scope: +- Evaluate `Unity MCP: Open Unity`. +- Evaluate minimal safe guidance for starting or managing the MCP server. +- Only include commands that do not hide risky behavior. + +Automated tests: +- Command registration and trust gating. +- Error handling for missing Unity/editor discovery issues. + +Debug logging to include: +- `openUnity:start` +- `openUnity:result` +- `openUnity:error` + +Manual validation: +1. Run the command from a trusted Unity workspace. +2. Confirm it only performs the documented action. + +### Slice 6: Packaging, docs, and hardening + +Scope: +- Add extension README, local install docs, release notes stub, and manual test matrix. +- Add extension-host smoke tests if needed. +- Prepare for future marketplace packaging without publishing. + +Automated tests: +- Build. +- Unit tests. +- Extension smoke test. +- Package smoke test. + +Debug logging to review: +- Ensure all logs stay structured and redact secrets. + +Manual validation: +1. Package the extension locally. +2. Install the VSIX in a clean VS Code profile. +3. Re-run slice 1 to 5 validation steps. + +## Security, Privacy, and Workspace Trust Rules + +- No telemetry in v1. +- Never log auth tokens, headers, prompts, file contents, or generated config bodies. +- Default to read-only behavior in untrusted workspaces. +- Require trusted workspace for any file mutation or Unity launch action. +- Do not auto-start destructive or code-executing flows. +- Keep all networked or editor-mutating behavior explicit and user-initiated. + +## Slice 1 Validation Steps + +### Validate in VS Code + +1. Open the `vscode-extension/` folder in VS Code. +2. Run `npm install`. +3. Run `npm run build`. +4. Press `F5` to start the Extension Development Host. +5. In the Extension Development Host: + - Open any non-Unity folder and run `Unity MCP: Check Status`. + - Open a Unity project folder and run `Unity MCP: Check Status`. + - Run `Unity MCP: Show Output`. +6. Confirm the output channel shows: + - workspace trust state + - selected workspace folder + - Unity markers detected + - plugin installed or missing + - `.vscode/mcp.json` present or missing + +### Validate with Unity + +Unity is not required for Slice 1. + +If you want a realistic signal: +1. Open a Unity project that already has `Assets/`, `ProjectSettings/`, and `Packages/manifest.json`. +2. If the Unity MCP package is installed, confirm the status command reports it. +3. If the package is not installed, confirm the status command reports it as missing. diff --git a/vscode-extension/.gitignore b/vscode-extension/.gitignore new file mode 100644 index 000000000..16ce28f98 --- /dev/null +++ b/vscode-extension/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +out/ +.vscode-test/ +*.vsix diff --git a/vscode-extension/.vscode/launch.json b/vscode-extension/.vscode/launch.json new file mode 100644 index 000000000..67d061788 --- /dev/null +++ b/vscode-extension/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Unity MCP Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "npm: build" + } + ] +} diff --git a/vscode-extension/.vscode/tasks.json b/vscode-extension/.vscode/tasks.json new file mode 100644 index 000000000..4387aef23 --- /dev/null +++ b/vscode-extension/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$tsc" + ], + "label": "npm: build" + } + ] +} diff --git a/vscode-extension/README.md b/vscode-extension/README.md new file mode 100644 index 000000000..439e392d7 --- /dev/null +++ b/vscode-extension/README.md @@ -0,0 +1,45 @@ +# Unity MCP VS Code Extension + +This package is the isolated VS Code extension workspace for GitHub issue `#613`. + +## Current Slice + +Slice 1 adds: + +- extension scaffold +- output channel logging +- Workspace Trust awareness +- Unity project detection +- Unity MCP plugin detection +- `.vscode/mcp.json` detection +- `Unity MCP: Check Status` +- `Unity MCP: Show Output` + +No files are modified in the opened workspace by this slice. + +## Local Development + +```bash +cd vscode-extension +npm install +npm run build +``` + +Then open the `vscode-extension/` folder in VS Code and press `F5`. + +## Manual Validation + +In the Extension Development Host: + +1. Open a non-Unity folder. +2. Run `Unity MCP: Check Status`. +3. Confirm the output channel reports `Unity project detected: no`. +4. Open a Unity project folder. +5. Run `Unity MCP: Check Status`. +6. Confirm the output channel reports: + - trust state + - Unity markers + - plugin installed or missing + - `.vscode/mcp.json` present or missing + +Unity itself is not required for this slice. diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json new file mode 100644 index 000000000..ea5098c14 --- /dev/null +++ b/vscode-extension/package-lock.json @@ -0,0 +1,1630 @@ +{ + "name": "unity-mcp-vscode", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "unity-mcp-vscode", + "version": "0.0.1", + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^22.15.0", + "@types/vscode": "^1.99.0", + "typescript": "^5.8.0", + "vitest": "^3.1.0" + }, + "engines": { + "vscode": "^1.99.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.120.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.120.0.tgz", + "integrity": "sha512-feaT4Rst+FkTch5zz/ZbNCxoIvo55YU80Be2kiL7OJcod4+CUYf2lUBPdIJzozNnSEMq1VRTGrWEcCGFB3fBmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.6", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz", + "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/vscode-extension/package.json b/vscode-extension/package.json new file mode 100644 index 000000000..e2f0ed250 --- /dev/null +++ b/vscode-extension/package.json @@ -0,0 +1,74 @@ +{ + "name": "unity-mcp-vscode", + "displayName": "Unity MCP - VS Code Integration", + "description": "Preview VS Code integration for Unity MCP local project setup and diagnostics.", + "version": "0.0.1", + "license": "Apache-2.0", + "engines": { + "vscode": "^1.99.0" + }, + "categories": [ + "Other" + ], + "keywords": [ + "unity", + "mcp", + "copilot", + "vscode" + ], + "main": "./out/extension.js", + "extensionKind": [ + "workspace" + ], + "activationEvents": [ + "onCommand:unityMcp.checkStatus", + "onCommand:unityMcp.showOutput" + ], + "capabilities": { + "untrustedWorkspaces": { + "supported": "limited", + "description": "Unity MCP only exposes read-only diagnostics in untrusted workspaces." + } + }, + "contributes": { + "commands": [ + { + "command": "unityMcp.checkStatus", + "title": "Unity MCP: Check Status" + }, + { + "command": "unityMcp.showOutput", + "title": "Unity MCP: Show Output" + } + ], + "configuration": { + "title": "Unity MCP", + "properties": { + "unityMcp.logLevel": { + "type": "string", + "enum": [ + "error", + "warn", + "info", + "debug" + ], + "default": "info", + "description": "Controls the verbosity of the Unity MCP extension output channel." + } + } + } + }, + "scripts": { + "build": "tsc -p ./", + "watch": "tsc -watch -p ./", + "pretest": "npm run build", + "test": "vitest run", + "test:watch": "vitest" + }, + "devDependencies": { + "@types/node": "^22.15.0", + "@types/vscode": "^1.99.0", + "typescript": "^5.8.0", + "vitest": "^3.1.0" + } +} diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts new file mode 100644 index 000000000..84ef1f1fa --- /dev/null +++ b/vscode-extension/src/extension.ts @@ -0,0 +1,111 @@ +import * as vscode from 'vscode'; +import { ExtensionLogger } from './logging'; +import { + formatWorkspaceStatusReport, + inspectWorkspaceStatus, +} from './projectStatus'; +import { pickWorkspaceFolder } from './workspace'; + +export async function activate(context: vscode.ExtensionContext): Promise { + const logger = new ExtensionLogger(); + context.subscriptions.push(logger); + + logger.info('activate:start', { + workspaceCount: vscode.workspace.workspaceFolders?.length ?? 0, + trusted: vscode.workspace.isTrusted, + }); + + context.subscriptions.push( + vscode.workspace.onDidGrantWorkspaceTrust(() => { + logger.info('trust:granted', {}); + }), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.showOutput', () => { + logger.show(); + logger.debug('output:show', {}); + }), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.checkStatus', async () => { + const workspaceFolder = await pickWorkspaceFolder(); + logger.debug('workspace:pick', { + selected: workspaceFolder?.uri.fsPath ?? null, + }); + + if (!workspaceFolder) { + void vscode.window.showWarningMessage( + 'Unity MCP needs an open workspace folder to inspect project status.', + ); + logger.warn('status:error', { + reason: 'no-workspace-folder', + }); + return; + } + + const trustState = vscode.workspace.isTrusted ? 'trusted' : 'restricted'; + + try { + logger.info('status:computeStart', { + workspace: workspaceFolder.uri.fsPath, + trustState, + }); + + const status = await inspectWorkspaceStatus( + workspaceFolder.uri.fsPath, + workspaceFolder.name, + trustState, + ); + + logger.info('status:computeResult', { + workspace: workspaceFolder.uri.fsPath, + unityProjectDetected: status.unityProjectDetected, + pluginInstalled: status.pluginInstalled, + mcpConfigExists: status.mcpConfigExists, + mcpServerConfigured: status.mcpServerConfigured, + }); + + logger.appendReport( + 'Unity MCP Status', + formatWorkspaceStatusReport(status), + ); + logger.show(); + + const summary = status.unityProjectDetected + ? `Unity MCP status collected for ${workspaceFolder.name}.` + : `${workspaceFolder.name} does not look like a Unity project.`; + + void vscode.window.showInformationMessage(summary, 'Show Output').then((selection) => { + if (selection === 'Show Output') { + logger.show(); + } + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error('status:error', { + workspace: workspaceFolder.uri.fsPath, + message, + }); + + void vscode.window.showErrorMessage( + `Unity MCP failed to inspect the workspace: ${message}`, + 'Show Output', + ).then((selection) => { + if (selection === 'Show Output') { + logger.show(); + } + }); + } + }), + ); + + logger.info('activate:complete', { + commands: ['unityMcp.checkStatus', 'unityMcp.showOutput'], + }); +} + +export function deactivate(): void { + // Nothing to dispose beyond the extension context subscriptions. +} diff --git a/vscode-extension/src/logging.ts b/vscode-extension/src/logging.ts new file mode 100644 index 000000000..e7bc391d6 --- /dev/null +++ b/vscode-extension/src/logging.ts @@ -0,0 +1,101 @@ +import * as vscode from 'vscode'; + +export type LogLevel = 'error' | 'warn' | 'info' | 'debug'; + +const logLevelWeight: Record = { + error: 0, + warn: 1, + info: 2, + debug: 3, +}; + +export class ExtensionLogger implements vscode.Disposable { + private readonly channel = vscode.window.createOutputChannel('Unity MCP'); + + public dispose(): void { + this.channel.dispose(); + } + + public show(preserveFocus = false): void { + this.channel.show(preserveFocus); + } + + public error(event: string, details?: Record): void { + this.write('error', event, details); + } + + public warn(event: string, details?: Record): void { + this.write('warn', event, details); + } + + public info(event: string, details?: Record): void { + this.write('info', event, details); + } + + public debug(event: string, details?: Record): void { + this.write('debug', event, details); + } + + public appendReport(title: string, body: string): void { + this.channel.appendLine(''); + this.channel.appendLine(`=== ${title} ===`); + for (const line of body.split('\n')) { + this.channel.appendLine(line); + } + } + + private write(level: LogLevel, event: string, details?: Record): void { + if (logLevelWeight[level] > logLevelWeight[this.currentLogLevel()]) { + return; + } + + const timestamp = new Date().toISOString(); + const suffix = details && Object.keys(details).length > 0 + ? ` ${serializeDetails(details)}` + : ''; + + this.channel.appendLine(`[${timestamp}] [${level}] ${event}${suffix}`); + } + + private currentLogLevel(): LogLevel { + const configured = vscode.workspace + .getConfiguration('unityMcp') + .get('logLevel', 'info'); + + if (configured === 'error' || configured === 'warn' || configured === 'info' || configured === 'debug') { + return configured; + } + + return 'info'; + } +} + +function serializeDetails(details: Record): string { + return Object.entries(details) + .map(([key, value]) => `${key}=${stringifyValue(value)}`) + .join(' '); +} + +function stringifyValue(value: unknown): string { + if (value === undefined) { + return 'undefined'; + } + + if (value === null) { + return 'null'; + } + + if (typeof value === 'string') { + return JSON.stringify(value); + } + + if (typeof value === 'number' || typeof value === 'boolean') { + return String(value); + } + + if (Array.isArray(value)) { + return `[${value.map((item) => stringifyValue(item)).join(',')}]`; + } + + return JSON.stringify(value); +} diff --git a/vscode-extension/src/projectStatus.test.ts b/vscode-extension/src/projectStatus.test.ts new file mode 100644 index 000000000..8aff937c4 --- /dev/null +++ b/vscode-extension/src/projectStatus.test.ts @@ -0,0 +1,68 @@ +import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; +import { + inspectWorkspaceStatus, +} from './projectStatus'; + +const tempRoots: string[] = []; + +afterEach(async () => { + await Promise.all( + tempRoots.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), + ); +}); + +describe('inspectWorkspaceStatus', () => { + it('detects a Unity project and installed Unity MCP plugin', async () => { + const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, 'Assets'), { recursive: true }); + await mkdir(path.join(workspace, 'ProjectSettings'), { recursive: true }); + await mkdir(path.join(workspace, 'Packages'), { recursive: true }); + await writeFile( + path.join(workspace, 'Packages', 'manifest.json'), + JSON.stringify({ + dependencies: { + 'com.ivanmurzak.unity.mcp': '0.79.0', + }, + }, null, 2), + ); + + const status = await inspectWorkspaceStatus(workspace, 'TestProject', 'trusted'); + + expect(status.unityProjectDetected).toBe(true); + expect(status.pluginInstalled).toBe(true); + expect(status.pluginVersion).toBe('0.79.0'); + expect(status.warnings).toEqual([]); + }); + + it('reports missing Unity markers for a non-Unity workspace', async () => { + const workspace = await createTempWorkspace(); + await writeFile(path.join(workspace, 'README.md'), '# hello\n'); + + const status = await inspectWorkspaceStatus(workspace, 'PlainFolder', 'restricted'); + + expect(status.unityProjectDetected).toBe(false); + expect(status.pluginInstalled).toBe(false); + expect(status.unityMarkers).toEqual([]); + }); + + it('warns on invalid workspace MCP config', async () => { + const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, '.vscode'), { recursive: true }); + await writeFile(path.join(workspace, '.vscode', 'mcp.json'), '{invalid json'); + + const status = await inspectWorkspaceStatus(workspace, 'BrokenConfig', 'trusted'); + + expect(status.mcpConfigExists).toBe(true); + expect(status.mcpServerConfigured).toBe(false); + expect(status.warnings.some((warning) => warning.includes('Could not parse .vscode/mcp.json'))).toBe(true); + }); +}); + +async function createTempWorkspace(): Promise { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'unity-mcp-vscode-')); + tempRoots.push(tempDir); + return tempDir; +} diff --git a/vscode-extension/src/projectStatus.ts b/vscode-extension/src/projectStatus.ts new file mode 100644 index 000000000..0d9454dd8 --- /dev/null +++ b/vscode-extension/src/projectStatus.ts @@ -0,0 +1,178 @@ +import { promises as fs } from 'node:fs'; +import * as path from 'node:path'; + +const MCP_SERVER_NAME = 'ai-game-developer'; +const UNITY_MCP_PACKAGE_NAME = 'com.ivanmurzak.unity.mcp'; + +export interface WorkspaceStatus { + workspaceName: string; + workspacePath: string; + trustState: 'trusted' | 'restricted'; + unityProjectDetected: boolean; + unityMarkers: string[]; + pluginInstalled: boolean; + pluginVersion?: string; + mcpConfigExists: boolean; + mcpServerConfigured: boolean; + mcpServerTransport?: string; + warnings: string[]; +} + +export async function inspectWorkspaceStatus( + workspacePath: string, + workspaceName: string, + trustState: 'trusted' | 'restricted', +): Promise { + const warnings: string[] = []; + const unityMarkers: string[] = []; + + const assetsPath = path.join(workspacePath, 'Assets'); + const projectSettingsPath = path.join(workspacePath, 'ProjectSettings'); + const packageManifestPath = path.join(workspacePath, 'Packages', 'manifest.json'); + const mcpConfigPath = path.join(workspacePath, '.vscode', 'mcp.json'); + + const hasAssets = await pathExists(assetsPath); + if (hasAssets) { + unityMarkers.push('Assets/'); + } + + const hasProjectSettings = await pathExists(projectSettingsPath); + if (hasProjectSettings) { + unityMarkers.push('ProjectSettings/'); + } + + const manifestInfo = await readPackageManifest(packageManifestPath); + if (manifestInfo.exists) { + unityMarkers.push('Packages/manifest.json'); + } + warnings.push(...manifestInfo.warnings); + + const unityProjectDetected = hasAssets && (hasProjectSettings || manifestInfo.exists); + + const pluginVersion = manifestInfo.dependencies[UNITY_MCP_PACKAGE_NAME]; + const pluginInstalled = typeof pluginVersion === 'string' && pluginVersion.length > 0; + + const mcpInfo = await readMcpConfig(mcpConfigPath); + warnings.push(...mcpInfo.warnings); + + return { + workspaceName, + workspacePath, + trustState, + unityProjectDetected, + unityMarkers, + pluginInstalled, + pluginVersion, + mcpConfigExists: mcpInfo.exists, + mcpServerConfigured: mcpInfo.hasServerEntry, + mcpServerTransport: mcpInfo.transport, + warnings, + }; +} + +export function formatWorkspaceStatusReport(status: WorkspaceStatus): string { + const lines = [ + `Workspace: ${status.workspaceName}`, + `Path: ${status.workspacePath}`, + `Workspace trust: ${status.trustState}`, + `Unity project detected: ${status.unityProjectDetected ? 'yes' : 'no'}`, + `Unity markers: ${status.unityMarkers.length > 0 ? status.unityMarkers.join(', ') : 'none'}`, + `Unity MCP plugin installed: ${status.pluginInstalled ? `yes (${status.pluginVersion ?? 'unknown version'})` : 'no'}`, + `.vscode/mcp.json present: ${status.mcpConfigExists ? 'yes' : 'no'}`, + `ai-game-developer configured: ${status.mcpServerConfigured ? 'yes' : 'no'}`, + `Configured transport: ${status.mcpServerTransport ?? 'unknown'}`, + ]; + + if (status.warnings.length > 0) { + lines.push('Warnings:'); + for (const warning of status.warnings) { + lines.push(`- ${warning}`); + } + } + + return lines.join('\n'); +} + +interface PackageManifestInfo { + exists: boolean; + dependencies: Record; + warnings: string[]; +} + +interface McpConfigInfo { + exists: boolean; + hasServerEntry: boolean; + transport?: string; + warnings: string[]; +} + +async function readPackageManifest(packageManifestPath: string): Promise { + if (!(await pathExists(packageManifestPath))) { + return { exists: false, dependencies: {}, warnings: [] }; + } + + try { + const raw = await fs.readFile(packageManifestPath, 'utf8'); + const parsed = JSON.parse(raw) as { dependencies?: Record }; + return { + exists: true, + dependencies: parsed.dependencies ?? {}, + warnings: [], + }; + } catch (error) { + return { + exists: true, + dependencies: {}, + warnings: [ + `Could not parse Packages/manifest.json: ${toErrorMessage(error)}`, + ], + }; + } +} + +async function readMcpConfig(mcpConfigPath: string): Promise { + if (!(await pathExists(mcpConfigPath))) { + return { + exists: false, + hasServerEntry: false, + warnings: [], + }; + } + + try { + const raw = await fs.readFile(mcpConfigPath, 'utf8'); + const parsed = JSON.parse(raw) as { + servers?: Record; + }; + + const serverEntry = parsed.servers?.[MCP_SERVER_NAME]; + + return { + exists: true, + hasServerEntry: Boolean(serverEntry), + transport: serverEntry?.type, + warnings: [], + }; + } catch (error) { + return { + exists: true, + hasServerEntry: false, + warnings: [ + `Could not parse .vscode/mcp.json: ${toErrorMessage(error)}`, + ], + }; + } +} + +async function pathExists(targetPath: string): Promise { + try { + await fs.access(targetPath); + return true; + } catch { + return false; + } +} + +function toErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/vscode-extension/src/workspace.ts b/vscode-extension/src/workspace.ts new file mode 100644 index 000000000..58e3b52c7 --- /dev/null +++ b/vscode-extension/src/workspace.ts @@ -0,0 +1,32 @@ +import * as vscode from 'vscode'; + +export async function pickWorkspaceFolder(): Promise { + const folders = vscode.workspace.workspaceFolders; + + if (!folders || folders.length === 0) { + return undefined; + } + + if (folders.length === 1) { + return folders[0]; + } + + const activeEditorUri = vscode.window.activeTextEditor?.document.uri; + if (activeEditorUri) { + const activeFolder = vscode.workspace.getWorkspaceFolder(activeEditorUri); + if (activeFolder) { + return activeFolder; + } + } + + return vscode.window.showQuickPick( + folders.map((folder) => ({ + label: folder.name, + description: folder.uri.fsPath, + folder, + })), + { + placeHolder: 'Choose a workspace folder to inspect for Unity MCP status', + }, + ).then((item) => item?.folder); +} diff --git a/vscode-extension/tsconfig.json b/vscode-extension/tsconfig.json new file mode 100644 index 000000000..fff3a1dbf --- /dev/null +++ b/vscode-extension/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": [ + "ES2022" + ], + "outDir": "out", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true, + "sourceMap": true, + "types": [ + "node", + "vscode" + ] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.test.ts" + ] +} From 394067c52550c6b976138155c56bbac69a4506e4 Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 10:50:50 -0500 Subject: [PATCH 03/12] feat: configure vscode mcp projects --- vscode-extension/README.md | 14 +- vscode-extension/package-lock.json | 68 ++++++++++ vscode-extension/package.json | 10 +- vscode-extension/src/cliAdapter.test.ts | 70 ++++++++++ vscode-extension/src/cliAdapter.ts | 113 ++++++++++++++++ vscode-extension/src/extension.ts | 167 +++++++++++++++++++++++- 6 files changed, 439 insertions(+), 3 deletions(-) create mode 100644 vscode-extension/src/cliAdapter.test.ts create mode 100644 vscode-extension/src/cliAdapter.ts diff --git a/vscode-extension/README.md b/vscode-extension/README.md index 439e392d7..f387d52f7 100644 --- a/vscode-extension/README.md +++ b/vscode-extension/README.md @@ -15,7 +15,12 @@ Slice 1 adds: - `Unity MCP: Check Status` - `Unity MCP: Show Output` -No files are modified in the opened workspace by this slice. +Slice 2 adds: + +- `Unity MCP: Configure Project` +- shared `unity-mcp-cli` adapter usage for VS Code MCP config generation +- trusted-workspace guard for write actions +- transport picker for `HTTP` or `STDIO` ## Local Development @@ -42,4 +47,11 @@ In the Extension Development Host: - plugin installed or missing - `.vscode/mcp.json` present or missing +Then: + +7. Run `Unity MCP: Configure Project`. +8. Choose `HTTP` unless you specifically want to test `STDIO`. +9. Confirm `.vscode/mcp.json` is created. +10. Run `Unity MCP: Check Status` again and confirm the MCP server now appears as configured. + Unity itself is not required for this slice. diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json index ea5098c14..47dd70291 100644 --- a/vscode-extension/package-lock.json +++ b/vscode-extension/package-lock.json @@ -8,6 +8,9 @@ "name": "unity-mcp-vscode", "version": "0.0.1", "license": "Apache-2.0", + "dependencies": { + "unity-mcp-cli": "^0.79.0" + }, "devDependencies": { "@types/node": "^22.15.0", "@types/vscode": "^1.99.0", @@ -1011,6 +1014,18 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/check-error": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", @@ -1021,6 +1036,15 @@ "node": ">= 16" } }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1438,6 +1462,23 @@ "dev": true, "license": "MIT" }, + "node_modules/unity-mcp-cli": { + "version": "0.79.0", + "resolved": "https://registry.npmjs.org/unity-mcp-cli/-/unity-mcp-cli-0.79.0.tgz", + "integrity": "sha512-8jbUi+btgutC2DaNkX56e+nxQ5ww0j8JtzOQPk7KbP2QkyPCJpRobqHzA5BxsbVKFQsN4/Cg7PgTmVxGdsiH8Q==", + "license": "Apache-2.0", + "dependencies": { + "chalk": "^5.6.2", + "commander": "^13.1.0", + "yocto-spinner": "^1.1.0" + }, + "bin": { + "unity-mcp-cli": "bin/unity-mcp-cli.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/vite": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", @@ -1625,6 +1666,33 @@ "engines": { "node": ">=8" } + }, + "node_modules/yocto-spinner": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.0.tgz", + "integrity": "sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/vscode-extension/package.json b/vscode-extension/package.json index e2f0ed250..9bc5561c7 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -22,7 +22,8 @@ ], "activationEvents": [ "onCommand:unityMcp.checkStatus", - "onCommand:unityMcp.showOutput" + "onCommand:unityMcp.showOutput", + "onCommand:unityMcp.configureProject" ], "capabilities": { "untrustedWorkspaces": { @@ -36,6 +37,10 @@ "command": "unityMcp.checkStatus", "title": "Unity MCP: Check Status" }, + { + "command": "unityMcp.configureProject", + "title": "Unity MCP: Configure Project" + }, { "command": "unityMcp.showOutput", "title": "Unity MCP: Show Output" @@ -65,6 +70,9 @@ "test": "vitest run", "test:watch": "vitest" }, + "dependencies": { + "unity-mcp-cli": "^0.79.0" + }, "devDependencies": { "@types/node": "^22.15.0", "@types/vscode": "^1.99.0", diff --git a/vscode-extension/src/cliAdapter.test.ts b/vscode-extension/src/cliAdapter.test.ts new file mode 100644 index 000000000..c1217984f --- /dev/null +++ b/vscode-extension/src/cliAdapter.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it, vi } from 'vitest'; +import { configureVscodeProject } from './cliAdapter'; +import { ExtensionLogger } from './logging'; + +describe('configureVscodeProject', () => { + it('calls setupMcp for the vscode-copilot agent', async () => { + const setupMcp = vi.fn().mockResolvedValue({ + kind: 'success', + success: true, + agentId: 'vscode-copilot', + configPath: '/tmp/project/.vscode/mcp.json', + transport: 'http', + warnings: [], + nextSteps: [], + }); + + const logger = createLogger(); + const result = await configureVscodeProject( + logger, + { + workspacePath: '/tmp/project', + transport: 'http', + }, + async () => ({ + setupMcp, + listAgentIds: () => ['vscode-copilot'], + }), + ); + + expect(setupMcp).toHaveBeenCalledWith( + expect.objectContaining({ + agentId: 'vscode-copilot', + unityProjectPath: '/tmp/project', + transport: 'http', + }), + ); + expect(result.kind).toBe('success'); + }); + + it('returns a failure when the adapter cannot load the CLI module', async () => { + const logger = createLogger(); + const result = await configureVscodeProject( + logger, + { + workspacePath: '/tmp/project', + transport: 'http', + }, + async () => { + throw new Error('load failed'); + }, + ); + + expect(result.kind).toBe('failure'); + if (result.kind === 'failure') { + expect(result.error.message).toContain('Could not load unity-mcp-cli'); + } + }); +}); + +function createLogger(): ExtensionLogger { + return { + dispose(): void {}, + show(): void {}, + error(): void {}, + warn(): void {}, + info(): void {}, + debug(): void {}, + appendReport(): void {}, + } as unknown as ExtensionLogger; +} diff --git a/vscode-extension/src/cliAdapter.ts b/vscode-extension/src/cliAdapter.ts new file mode 100644 index 000000000..ff7d06d1a --- /dev/null +++ b/vscode-extension/src/cliAdapter.ts @@ -0,0 +1,113 @@ +import type { + ProgressEvent, + SetupMcpOptions, + SetupMcpResult, +} from 'unity-mcp-cli'; +import { ExtensionLogger } from './logging'; + +export type CliModuleLoader = () => Promise; + +type NativeImport = (specifier: string) => Promise; + +interface UnityMcpCliModule { + setupMcp(options: SetupMcpOptions): Promise; + listAgentIds(): string[]; +} + +export async function configureVscodeProject( + logger: ExtensionLogger, + options: ConfigureVscodeProjectOptions, + loader: CliModuleLoader = defaultLoader, +): Promise { + logger.debug('cliAdapter:loadStart', {}); + + let cliModule: UnityMcpCliModule; + try { + cliModule = await loader(); + logger.debug('cliAdapter:loadSuccess', { + availableAgents: cliModule.listAgentIds(), + }); + } catch (error) { + const message = toErrorMessage(error); + logger.error('cliAdapter:loadFailure', { + message, + }); + + return { + kind: 'failure', + success: false, + warnings: [], + nextSteps: [], + error: new Error(`Could not load unity-mcp-cli: ${message}`), + }; + } + + if (!cliModule.listAgentIds().includes('vscode-copilot')) { + logger.error('cliAdapter:loadFailure', { + reason: 'missing-agent-id', + }); + + return { + kind: 'failure', + success: false, + warnings: [], + nextSteps: [], + error: new Error('unity-mcp-cli does not expose the vscode-copilot agent configuration.'), + }; + } + + logger.info('cliAdapter:callStart', { + workspacePath: options.workspacePath, + transport: options.transport, + }); + + const result = await cliModule.setupMcp({ + agentId: 'vscode-copilot', + unityProjectPath: options.workspacePath, + transport: options.transport, + onProgress: (event) => { + logger.debug('cliAdapter:progress', { + phase: event.phase, + message: event.message, + }); + options.onProgress?.(event); + }, + }); + + if (result.kind === 'success') { + logger.info('cliAdapter:callSuccess', { + configPath: result.configPath, + transport: result.transport, + warnings: result.warnings.length, + }); + return result; + } + + logger.error('cliAdapter:callFailure', { + message: result.error.message, + warnings: result.warnings.length, + }); + return result; +} + +async function defaultLoader(): Promise { + // Keep native ESM loading at runtime. TypeScript rewrites plain + // `import()` into `require()` for CommonJS output, which breaks on + // packages like `unity-mcp-cli` that only expose ESM import exports. + return nativeImport('unity-mcp-cli'); +} + +const nativeImport = new Function( + 'specifier', + 'return import(specifier);', +) as NativeImport; + +function toErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +export interface ConfigureVscodeProjectOptions { + workspacePath: string; + transport: 'http' | 'stdio'; + onProgress?: (event: ProgressEvent) => void; +} diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index 84ef1f1fa..d2ed5c376 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { configureVscodeProject } from './cliAdapter'; import { ExtensionLogger } from './logging'; import { formatWorkspaceStatusReport, @@ -28,6 +29,166 @@ export async function activate(context: vscode.ExtensionContext): Promise }), ); + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.configureProject', async () => { + const workspaceFolder = await pickWorkspaceFolder(); + logger.debug('workspace:pick', { + selected: workspaceFolder?.uri.fsPath ?? null, + }); + + if (!workspaceFolder) { + void vscode.window.showWarningMessage( + 'Unity MCP needs an open workspace folder before it can write project configuration.', + ); + logger.warn('configure:error', { + reason: 'no-workspace-folder', + }); + return; + } + + if (!vscode.workspace.isTrusted) { + logger.warn('configure:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'workspace-not-trusted', + }); + + const selection = await vscode.window.showWarningMessage( + 'Unity MCP only writes project configuration in a trusted workspace.', + 'Manage Trust', + ); + + if (selection === 'Manage Trust') { + await vscode.commands.executeCommand('workbench.trust.manage'); + } + return; + } + + const initialStatus = await inspectWorkspaceStatus( + workspaceFolder.uri.fsPath, + workspaceFolder.name, + 'trusted', + ); + + if (!initialStatus.unityProjectDetected) { + logger.warn('configure:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'not-unity-project', + }); + + void vscode.window.showErrorMessage( + 'Unity MCP can only configure a workspace that looks like a Unity project.', + ); + return; + } + + if (!initialStatus.pluginInstalled) { + const pluginChoice = await vscode.window.showWarningMessage( + 'Unity MCP plugin was not detected in Packages/manifest.json. Continue writing .vscode/mcp.json anyway?', + 'Continue', + 'Cancel', + ); + + if (pluginChoice !== 'Continue') { + logger.warn('configure:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'plugin-missing-cancelled', + }); + return; + } + } + + const transportChoice = await vscode.window.showQuickPick( + [ + { + label: 'HTTP', + description: 'Recommended', + detail: 'Writes an HTTP MCP server entry into .vscode/mcp.json.', + transport: 'http' as const, + }, + { + label: 'STDIO', + detail: 'Writes a stdio MCP server entry that points to the local Unity MCP server binary.', + transport: 'stdio' as const, + }, + ], + { + placeHolder: 'Select which transport Unity MCP should configure for VS Code', + }, + ); + + if (!transportChoice) { + logger.warn('configure:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'transport-not-selected', + }); + return; + } + + logger.info('configure:start', { + workspace: workspaceFolder.uri.fsPath, + transport: transportChoice.transport, + }); + + const result = await configureVscodeProject(logger, { + workspacePath: workspaceFolder.uri.fsPath, + transport: transportChoice.transport, + }); + + if (result.kind === 'failure') { + void vscode.window.showErrorMessage( + `Unity MCP could not configure the project: ${result.error.message}`, + 'Show Output', + ).then((selection) => { + if (selection === 'Show Output') { + logger.show(); + } + }); + return; + } + + logger.info('configure:writeSuccess', { + configPath: result.configPath, + transport: result.transport, + }); + if (result.warnings.length > 0) { + logger.warn('configure:warnings', { + warnings: result.warnings, + }); + } + if (result.nextSteps.length > 0) { + logger.info('configure:nextSteps', { + nextSteps: result.nextSteps, + }); + } + + const updatedStatus = await inspectWorkspaceStatus( + workspaceFolder.uri.fsPath, + workspaceFolder.name, + 'trusted', + ); + logger.appendReport( + 'Unity MCP Status', + formatWorkspaceStatusReport(updatedStatus), + ); + logger.show(); + + void vscode.window.showInformationMessage( + `Unity MCP configured ${workspaceFolder.name} for VS Code (${result.transport}).`, + 'Open Config', + 'Show Output', + ).then(async (selection) => { + if (selection === 'Open Config') { + const document = await vscode.workspace.openTextDocument(result.configPath); + await vscode.window.showTextDocument(document); + } + + if (selection === 'Show Output') { + logger.show(); + } + }); + }), + ); + context.subscriptions.push( vscode.commands.registerCommand('unityMcp.checkStatus', async () => { const workspaceFolder = await pickWorkspaceFolder(); @@ -102,7 +263,11 @@ export async function activate(context: vscode.ExtensionContext): Promise ); logger.info('activate:complete', { - commands: ['unityMcp.checkStatus', 'unityMcp.showOutput'], + commands: [ + 'unityMcp.checkStatus', + 'unityMcp.configureProject', + 'unityMcp.showOutput', + ], }); } From 4705962f779ba082c4fdc713ac30bf21d55fd217 Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 11:02:54 -0500 Subject: [PATCH 04/12] feat: install unity mcp plugin from vscode --- vscode-extension/README.md | 13 +++ vscode-extension/package.json | 7 +- vscode-extension/src/cliAdapter.test.ts | 33 +++++- vscode-extension/src/cliAdapter.ts | 68 +++++++++++ vscode-extension/src/extension.ts | 144 +++++++++++++++++++++++- 5 files changed, 262 insertions(+), 3 deletions(-) diff --git a/vscode-extension/README.md b/vscode-extension/README.md index f387d52f7..2da37967b 100644 --- a/vscode-extension/README.md +++ b/vscode-extension/README.md @@ -22,6 +22,12 @@ Slice 2 adds: - trusted-workspace guard for write actions - transport picker for `HTTP` or `STDIO` +Slice 3 adds: + +- `Unity MCP: Install Plugin` +- shared `unity-mcp-cli` adapter usage for Unity package installation +- explicit confirmation before mutating `Packages/manifest.json` + ## Local Development ```bash @@ -54,4 +60,11 @@ Then: 9. Confirm `.vscode/mcp.json` is created. 10. Run `Unity MCP: Check Status` again and confirm the MCP server now appears as configured. +Then: + +11. Run `Unity MCP: Install Plugin`. +12. Confirm the warning about updating `Packages/manifest.json`. +13. Approve the install. +14. Confirm the manifest is updated and the status report now shows the plugin as installed. + Unity itself is not required for this slice. diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 9bc5561c7..0966f3e45 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -23,7 +23,8 @@ "activationEvents": [ "onCommand:unityMcp.checkStatus", "onCommand:unityMcp.showOutput", - "onCommand:unityMcp.configureProject" + "onCommand:unityMcp.configureProject", + "onCommand:unityMcp.installPlugin" ], "capabilities": { "untrustedWorkspaces": { @@ -41,6 +42,10 @@ "command": "unityMcp.configureProject", "title": "Unity MCP: Configure Project" }, + { + "command": "unityMcp.installPlugin", + "title": "Unity MCP: Install Plugin" + }, { "command": "unityMcp.showOutput", "title": "Unity MCP: Show Output" diff --git a/vscode-extension/src/cliAdapter.test.ts b/vscode-extension/src/cliAdapter.test.ts index c1217984f..514b21bf8 100644 --- a/vscode-extension/src/cliAdapter.test.ts +++ b/vscode-extension/src/cliAdapter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { configureVscodeProject } from './cliAdapter'; +import { configureVscodeProject, installUnityMcpPlugin } from './cliAdapter'; import { ExtensionLogger } from './logging'; describe('configureVscodeProject', () => { @@ -55,6 +55,37 @@ describe('configureVscodeProject', () => { expect(result.error.message).toContain('Could not load unity-mcp-cli'); } }); + + it('calls installPlugin through the shared CLI loader', async () => { + const installPlugin = vi.fn().mockResolvedValue({ + kind: 'success', + success: true, + installedVersion: '0.79.0', + manifestPath: '/tmp/project/Packages/manifest.json', + warnings: [], + nextSteps: ['Open the Unity project in the Editor to complete installation.'], + }); + + const logger = createLogger(); + const result = await installUnityMcpPlugin( + logger, + { + workspacePath: '/tmp/project', + }, + async () => ({ + installPlugin, + setupMcp: vi.fn(), + listAgentIds: () => ['vscode-copilot'], + }), + ); + + expect(installPlugin).toHaveBeenCalledWith( + expect.objectContaining({ + unityProjectPath: '/tmp/project', + }), + ); + expect(result.kind).toBe('success'); + }); }); function createLogger(): ExtensionLogger { diff --git a/vscode-extension/src/cliAdapter.ts b/vscode-extension/src/cliAdapter.ts index ff7d06d1a..6c43430fa 100644 --- a/vscode-extension/src/cliAdapter.ts +++ b/vscode-extension/src/cliAdapter.ts @@ -1,4 +1,6 @@ import type { + InstallPluginOptions, + InstallResult, ProgressEvent, SetupMcpOptions, SetupMcpResult, @@ -10,6 +12,7 @@ export type CliModuleLoader = () => Promise; type NativeImport = (specifier: string) => Promise; interface UnityMcpCliModule { + installPlugin(options: InstallPluginOptions): Promise; setupMcp(options: SetupMcpOptions): Promise; listAgentIds(): string[]; } @@ -90,6 +93,65 @@ export async function configureVscodeProject( return result; } +export async function installUnityMcpPlugin( + logger: ExtensionLogger, + options: InstallUnityMcpPluginOptions, + loader: CliModuleLoader = defaultLoader, +): Promise { + logger.debug('cliAdapter:loadStart', {}); + + let cliModule: UnityMcpCliModule; + try { + cliModule = await loader(); + logger.debug('cliAdapter:loadSuccess', {}); + } catch (error) { + const message = toErrorMessage(error); + logger.error('cliAdapter:loadFailure', { + message, + }); + + return { + kind: 'failure', + success: false, + warnings: [], + nextSteps: [], + error: new Error(`Could not load unity-mcp-cli: ${message}`), + }; + } + + logger.info('cliAdapter:callStart', { + workspacePath: options.workspacePath, + operation: 'installPlugin', + }); + + const result = await cliModule.installPlugin({ + unityProjectPath: options.workspacePath, + version: options.version, + onProgress: (event) => { + logger.debug('cliAdapter:progress', { + phase: event.phase, + message: event.message, + }); + options.onProgress?.(event); + }, + }); + + if (result.kind === 'success') { + logger.info('cliAdapter:callSuccess', { + manifestPath: result.manifestPath, + installedVersion: result.installedVersion, + warnings: result.warnings.length, + }); + return result; + } + + logger.error('cliAdapter:callFailure', { + message: result.error.message, + warnings: result.warnings.length, + }); + return result; +} + async function defaultLoader(): Promise { // Keep native ESM loading at runtime. TypeScript rewrites plain // `import()` into `require()` for CommonJS output, which breaks on @@ -111,3 +173,9 @@ export interface ConfigureVscodeProjectOptions { transport: 'http' | 'stdio'; onProgress?: (event: ProgressEvent) => void; } + +export interface InstallUnityMcpPluginOptions { + workspacePath: string; + version?: string; + onProgress?: (event: ProgressEvent) => void; +} diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index d2ed5c376..b93ad1465 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { configureVscodeProject } from './cliAdapter'; +import { configureVscodeProject, installUnityMcpPlugin } from './cliAdapter'; import { ExtensionLogger } from './logging'; import { formatWorkspaceStatusReport, @@ -29,6 +29,147 @@ export async function activate(context: vscode.ExtensionContext): Promise }), ); + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.installPlugin', async () => { + const workspaceFolder = await pickWorkspaceFolder(); + logger.debug('workspace:pick', { + selected: workspaceFolder?.uri.fsPath ?? null, + }); + + if (!workspaceFolder) { + void vscode.window.showWarningMessage( + 'Unity MCP needs an open workspace folder before it can install the Unity package.', + ); + logger.warn('pluginInstall:error', { + reason: 'no-workspace-folder', + }); + return; + } + + if (!vscode.workspace.isTrusted) { + logger.warn('pluginInstall:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'workspace-not-trusted', + }); + + const selection = await vscode.window.showWarningMessage( + 'Unity MCP only installs the Unity package in a trusted workspace.', + 'Manage Trust', + ); + + if (selection === 'Manage Trust') { + await vscode.commands.executeCommand('workbench.trust.manage'); + } + return; + } + + const initialStatus = await inspectWorkspaceStatus( + workspaceFolder.uri.fsPath, + workspaceFolder.name, + 'trusted', + ); + + if (!initialStatus.unityProjectDetected) { + logger.warn('pluginInstall:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'not-unity-project', + }); + + void vscode.window.showErrorMessage( + 'Unity MCP can only install the Unity package into a workspace that looks like a Unity project.', + ); + return; + } + + if (initialStatus.pluginInstalled) { + const alreadyInstalledChoice = await vscode.window.showInformationMessage( + `Unity MCP plugin already appears in ${workspaceFolder.name}. Install again anyway to let the shared library reconcile the manifest?`, + 'Re-run Install', + 'Cancel', + ); + + if (alreadyInstalledChoice !== 'Re-run Install') { + logger.warn('pluginInstall:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'already-installed-cancelled', + }); + return; + } + } else { + const confirmInstall = await vscode.window.showWarningMessage( + 'Unity MCP will update Packages/manifest.json in this Unity project. Continue?', + 'Install Plugin', + 'Cancel', + ); + + if (confirmInstall !== 'Install Plugin') { + logger.warn('pluginInstall:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'install-cancelled', + }); + return; + } + } + + logger.info('pluginInstall:start', { + workspace: workspaceFolder.uri.fsPath, + }); + + const result = await installUnityMcpPlugin(logger, { + workspacePath: workspaceFolder.uri.fsPath, + }); + + if (result.kind === 'failure') { + void vscode.window.showErrorMessage( + `Unity MCP could not install the plugin: ${result.error.message}`, + 'Show Output', + ).then((selection) => { + if (selection === 'Show Output') { + logger.show(); + } + }); + return; + } + + if (result.warnings.length > 0) { + logger.warn('pluginInstall:warnings', { + warnings: result.warnings, + }); + } + if (result.nextSteps.length > 0) { + logger.info('pluginInstall:nextSteps', { + nextSteps: result.nextSteps, + }); + } + + const updatedStatus = await inspectWorkspaceStatus( + workspaceFolder.uri.fsPath, + workspaceFolder.name, + 'trusted', + ); + logger.appendReport( + 'Unity MCP Status', + formatWorkspaceStatusReport(updatedStatus), + ); + logger.show(); + + void vscode.window.showInformationMessage( + `Unity MCP plugin installed for ${workspaceFolder.name} (version ${result.installedVersion}).`, + 'Open Manifest', + 'Show Output', + ).then(async (selection) => { + if (selection === 'Open Manifest') { + const document = await vscode.workspace.openTextDocument(result.manifestPath); + await vscode.window.showTextDocument(document); + } + + if (selection === 'Show Output') { + logger.show(); + } + }); + }), + ); + context.subscriptions.push( vscode.commands.registerCommand('unityMcp.configureProject', async () => { const workspaceFolder = await pickWorkspaceFolder(); @@ -266,6 +407,7 @@ export async function activate(context: vscode.ExtensionContext): Promise commands: [ 'unityMcp.checkStatus', 'unityMcp.configureProject', + 'unityMcp.installPlugin', 'unityMcp.showOutput', ], }); From 6df089084b8c1d4c0bcc2e90d9e373ce43bbc402 Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 11:36:56 -0500 Subject: [PATCH 05/12] feat: open unity from vscode --- vscode-extension/README.md | 15 ++ vscode-extension/package.json | 7 +- vscode-extension/src/cliAdapter.test.ts | 37 ++++- vscode-extension/src/cliAdapter.ts | 84 +++++++++++ vscode-extension/src/extension.ts | 166 ++++++++++++++++++++- vscode-extension/src/projectStatus.test.ts | 31 ++++ vscode-extension/src/projectStatus.ts | 12 ++ vscode-extension/src/unityConfig.test.ts | 58 +++++++ vscode-extension/src/unityConfig.ts | 74 +++++++++ 9 files changed, 481 insertions(+), 3 deletions(-) create mode 100644 vscode-extension/src/unityConfig.test.ts create mode 100644 vscode-extension/src/unityConfig.ts diff --git a/vscode-extension/README.md b/vscode-extension/README.md index 2da37967b..7a4cfe875 100644 --- a/vscode-extension/README.md +++ b/vscode-extension/README.md @@ -28,6 +28,12 @@ Slice 3 adds: - shared `unity-mcp-cli` adapter usage for Unity package installation - explicit confirmation before mutating `Packages/manifest.json` +Slice 4 adds: + +- `Unity MCP: Open Unity` +- plain launch or MCP-connected launch +- reuse of shared `openProject()` behavior from `unity-mcp-cli` + ## Local Development ```bash @@ -67,4 +73,13 @@ Then: 13. Approve the install. 14. Confirm the manifest is updated and the status report now shows the plugin as installed. +Then: + +15. Run `Unity MCP: Open Unity`. +16. First test `Open Unity`. +17. Then test `Open Unity With MCP Connection`. +18. If the plugin was just installed and the project has not initialized yet, the extension should offer a guided fallback to `Open Without MCP`. +19. After Unity imports and creates `UserSettings/AI-Game-Developer-Config.json`, retry `Open Unity With MCP Connection`. +20. Confirm the output channel shows the open-project progress events. + Unity itself is not required for this slice. diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 0966f3e45..5d62f7336 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -24,7 +24,8 @@ "onCommand:unityMcp.checkStatus", "onCommand:unityMcp.showOutput", "onCommand:unityMcp.configureProject", - "onCommand:unityMcp.installPlugin" + "onCommand:unityMcp.installPlugin", + "onCommand:unityMcp.openUnity" ], "capabilities": { "untrustedWorkspaces": { @@ -46,6 +47,10 @@ "command": "unityMcp.installPlugin", "title": "Unity MCP: Install Plugin" }, + { + "command": "unityMcp.openUnity", + "title": "Unity MCP: Open Unity" + }, { "command": "unityMcp.showOutput", "title": "Unity MCP: Show Output" diff --git a/vscode-extension/src/cliAdapter.test.ts b/vscode-extension/src/cliAdapter.test.ts index 514b21bf8..a78d39506 100644 --- a/vscode-extension/src/cliAdapter.test.ts +++ b/vscode-extension/src/cliAdapter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { configureVscodeProject, installUnityMcpPlugin } from './cliAdapter'; +import { configureVscodeProject, installUnityMcpPlugin, openUnityProject } from './cliAdapter'; import { ExtensionLogger } from './logging'; describe('configureVscodeProject', () => { @@ -86,6 +86,41 @@ describe('configureVscodeProject', () => { ); expect(result.kind).toBe('success'); }); + + it('calls openProject through the shared CLI loader', async () => { + const openProject = vi.fn().mockResolvedValue({ + kind: 'success', + success: true, + editorPath: '/Applications/Unity/Unity.app', + editorPid: 1234, + projectPath: '/tmp/project', + warnings: [], + alreadyRunning: false, + }); + + const logger = createLogger(); + const result = await openUnityProject( + logger, + { + workspacePath: '/tmp/project', + noConnect: true, + }, + async () => ({ + installPlugin: vi.fn(), + openProject, + setupMcp: vi.fn(), + listAgentIds: () => ['vscode-copilot'], + }), + ); + + expect(openProject).toHaveBeenCalledWith( + expect.objectContaining({ + projectPath: '/tmp/project', + noConnect: true, + }), + ); + expect(result.kind).toBe('success'); + }); }); function createLogger(): ExtensionLogger { diff --git a/vscode-extension/src/cliAdapter.ts b/vscode-extension/src/cliAdapter.ts index 6c43430fa..2ceed55e5 100644 --- a/vscode-extension/src/cliAdapter.ts +++ b/vscode-extension/src/cliAdapter.ts @@ -1,6 +1,8 @@ import type { InstallPluginOptions, InstallResult, + OpenProjectOptions, + OpenProjectResult, ProgressEvent, SetupMcpOptions, SetupMcpResult, @@ -13,6 +15,7 @@ type NativeImport = (specifier: string) => Promise; interface UnityMcpCliModule { installPlugin(options: InstallPluginOptions): Promise; + openProject(options: OpenProjectOptions): Promise; setupMcp(options: SetupMcpOptions): Promise; listAgentIds(): string[]; } @@ -152,6 +155,75 @@ export async function installUnityMcpPlugin( return result; } +export async function openUnityProject( + logger: ExtensionLogger, + options: OpenUnityProjectOptions, + loader: CliModuleLoader = defaultLoader, +): Promise { + logger.debug('cliAdapter:loadStart', {}); + + let cliModule: UnityMcpCliModule; + try { + cliModule = await loader(); + logger.debug('cliAdapter:loadSuccess', {}); + } catch (error) { + const message = toErrorMessage(error); + logger.error('cliAdapter:loadFailure', { + message, + }); + + return { + kind: 'failure', + success: false, + warnings: [], + errorMessage: `Could not load unity-mcp-cli: ${message}`, + error: new Error(`Could not load unity-mcp-cli: ${message}`), + }; + } + + logger.info('cliAdapter:callStart', { + workspacePath: options.workspacePath, + operation: 'openProject', + noConnect: options.noConnect, + startServer: options.startServer, + transport: options.transport, + }); + + const result = await cliModule.openProject({ + projectPath: options.workspacePath, + noConnect: options.noConnect, + url: options.url, + token: options.token, + auth: options.auth, + keepConnected: options.keepConnected, + transport: options.transport, + startServer: options.startServer, + onProgress: (event) => { + logger.debug('cliAdapter:progress', { + phase: event.phase, + message: event.message, + }); + options.onProgress?.(event); + }, + }); + + if (result.kind === 'success') { + logger.info('cliAdapter:callSuccess', { + editorPath: result.editorPath, + editorPid: result.editorPid ?? null, + alreadyRunning: result.alreadyRunning ?? false, + warnings: result.warnings.length, + }); + return result; + } + + logger.error('cliAdapter:callFailure', { + message: result.error.message, + warnings: result.warnings.length, + }); + return result; +} + async function defaultLoader(): Promise { // Keep native ESM loading at runtime. TypeScript rewrites plain // `import()` into `require()` for CommonJS output, which breaks on @@ -179,3 +251,15 @@ export interface InstallUnityMcpPluginOptions { version?: string; onProgress?: (event: ProgressEvent) => void; } + +export interface OpenUnityProjectOptions { + workspacePath: string; + noConnect?: boolean; + url?: string; + token?: string; + auth?: 'none' | 'required'; + keepConnected?: boolean; + transport?: 'streamableHttp' | 'stdio'; + startServer?: boolean; + onProgress?: (event: ProgressEvent) => void; +} diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index b93ad1465..cc282c8d5 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -1,10 +1,11 @@ import * as vscode from 'vscode'; -import { configureVscodeProject, installUnityMcpPlugin } from './cliAdapter'; +import { configureVscodeProject, installUnityMcpPlugin, openUnityProject } from './cliAdapter'; import { ExtensionLogger } from './logging'; import { formatWorkspaceStatusReport, inspectWorkspaceStatus, } from './projectStatus'; +import { readUnityMcpProjectConfig } from './unityConfig'; import { pickWorkspaceFolder } from './workspace'; export async function activate(context: vscode.ExtensionContext): Promise { @@ -29,6 +30,168 @@ export async function activate(context: vscode.ExtensionContext): Promise }), ); + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.openUnity', async () => { + const workspaceFolder = await pickWorkspaceFolder(); + logger.debug('workspace:pick', { + selected: workspaceFolder?.uri.fsPath ?? null, + }); + + if (!workspaceFolder) { + void vscode.window.showWarningMessage( + 'Unity MCP needs an open workspace folder before it can launch Unity.', + ); + logger.warn('openUnity:error', { + reason: 'no-workspace-folder', + }); + return; + } + + if (!vscode.workspace.isTrusted) { + logger.warn('openUnity:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'workspace-not-trusted', + }); + + const selection = await vscode.window.showWarningMessage( + 'Unity MCP only launches Unity from a trusted workspace.', + 'Manage Trust', + ); + + if (selection === 'Manage Trust') { + await vscode.commands.executeCommand('workbench.trust.manage'); + } + return; + } + + const initialStatus = await inspectWorkspaceStatus( + workspaceFolder.uri.fsPath, + workspaceFolder.name, + 'trusted', + ); + + if (!initialStatus.unityProjectDetected) { + logger.warn('openUnity:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'not-unity-project', + }); + + void vscode.window.showErrorMessage( + 'Unity MCP can only open a workspace that looks like a Unity project.', + ); + return; + } + + const projectConfig = await readUnityMcpProjectConfig(workspaceFolder.uri.fsPath); + if (projectConfig.warnings.length > 0) { + logger.warn('openUnity:configWarnings', { + warnings: projectConfig.warnings, + }); + } + + const openMode = await vscode.window.showQuickPick( + [ + { + label: 'Open Unity', + detail: 'Launch the Unity project without overriding MCP connection settings.', + mode: 'plain' as const, + }, + { + label: 'Open Unity With MCP Connection', + detail: projectConfig.exists + ? 'Use the current AI-Game-Developer project config and request server startup.' + : 'Requires UserSettings/AI-Game-Developer-Config.json to be present.', + mode: 'connected' as const, + }, + ], + { + placeHolder: 'Choose how Unity MCP should launch this Unity project', + }, + ); + + if (!openMode) { + logger.warn('openUnity:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'mode-not-selected', + }); + return; + } + + let effectiveMode = openMode.mode; + if (effectiveMode === 'connected' && !projectConfig.exists) { + logger.warn('openUnity:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'project-config-missing', + }); + + const selection = await vscode.window.showWarningMessage( + 'Unity MCP is installed, but the project has not finished first-time initialization yet. Open Unity once without MCP so the package can import and create its project config, then retry connected launch.', + 'Open Without MCP', + 'Show Output', + 'Cancel', + ); + + if (selection === 'Show Output') { + logger.show(); + } + + if (selection !== 'Open Without MCP') { + return; + } + + effectiveMode = 'plain'; + } + + logger.info('openUnity:start', { + workspace: workspaceFolder.uri.fsPath, + mode: effectiveMode, + }); + + const result = await openUnityProject(logger, { + workspacePath: workspaceFolder.uri.fsPath, + noConnect: effectiveMode === 'plain', + url: effectiveMode === 'connected' ? projectConfig.host : undefined, + token: effectiveMode === 'connected' ? projectConfig.token : undefined, + auth: effectiveMode === 'connected' ? projectConfig.authOption : undefined, + keepConnected: effectiveMode === 'connected' ? projectConfig.keepConnected : undefined, + transport: effectiveMode === 'connected' ? projectConfig.transport : undefined, + startServer: effectiveMode === 'connected' ? true : undefined, + }); + + if (result.kind === 'failure') { + void vscode.window.showErrorMessage( + `Unity MCP could not open Unity: ${result.errorMessage}`, + 'Show Output', + ).then((selection) => { + if (selection === 'Show Output') { + logger.show(); + } + }); + return; + } + + if (result.warnings.length > 0) { + logger.warn('openUnity:warnings', { + warnings: result.warnings, + }); + } + + logger.show(); + const summary = result.alreadyRunning + ? `Unity is already running for ${workspaceFolder.name}.` + : `Unity launch requested for ${workspaceFolder.name}.`; + + void vscode.window.showInformationMessage( + summary, + 'Show Output', + ).then((selection) => { + if (selection === 'Show Output') { + logger.show(); + } + }); + }), + ); + context.subscriptions.push( vscode.commands.registerCommand('unityMcp.installPlugin', async () => { const workspaceFolder = await pickWorkspaceFolder(); @@ -408,6 +571,7 @@ export async function activate(context: vscode.ExtensionContext): Promise 'unityMcp.checkStatus', 'unityMcp.configureProject', 'unityMcp.installPlugin', + 'unityMcp.openUnity', 'unityMcp.showOutput', ], }); diff --git a/vscode-extension/src/projectStatus.test.ts b/vscode-extension/src/projectStatus.test.ts index 8aff937c4..2b2c3830e 100644 --- a/vscode-extension/src/projectStatus.test.ts +++ b/vscode-extension/src/projectStatus.test.ts @@ -20,6 +20,7 @@ describe('inspectWorkspaceStatus', () => { await mkdir(path.join(workspace, 'Assets'), { recursive: true }); await mkdir(path.join(workspace, 'ProjectSettings'), { recursive: true }); await mkdir(path.join(workspace, 'Packages'), { recursive: true }); + await mkdir(path.join(workspace, 'UserSettings'), { recursive: true }); await writeFile( path.join(workspace, 'Packages', 'manifest.json'), JSON.stringify({ @@ -28,12 +29,21 @@ describe('inspectWorkspaceStatus', () => { }, }, null, 2), ); + await writeFile( + path.join(workspace, 'UserSettings', 'AI-Game-Developer-Config.json'), + JSON.stringify({ + host: 'http://localhost:6501', + authOption: 'none', + transportMethod: 'streamableHttp', + }, null, 2), + ); const status = await inspectWorkspaceStatus(workspace, 'TestProject', 'trusted'); expect(status.unityProjectDetected).toBe(true); expect(status.pluginInstalled).toBe(true); expect(status.pluginVersion).toBe('0.79.0'); + expect(status.unityMcpProjectConfigExists).toBe(true); expect(status.warnings).toEqual([]); }); @@ -48,6 +58,27 @@ describe('inspectWorkspaceStatus', () => { expect(status.unityMarkers).toEqual([]); }); + it('warns when the plugin is installed but the Unity MCP project config has not been initialized yet', async () => { + const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, 'Assets'), { recursive: true }); + await mkdir(path.join(workspace, 'ProjectSettings'), { recursive: true }); + await mkdir(path.join(workspace, 'Packages'), { recursive: true }); + await writeFile( + path.join(workspace, 'Packages', 'manifest.json'), + JSON.stringify({ + dependencies: { + 'com.ivanmurzak.unity.mcp': '0.79.0', + }, + }, null, 2), + ); + + const status = await inspectWorkspaceStatus(workspace, 'NeedsInit', 'trusted'); + + expect(status.pluginInstalled).toBe(true); + expect(status.unityMcpProjectConfigExists).toBe(false); + expect(status.warnings.some((warning) => warning.includes('Open Unity once without MCP'))).toBe(true); + }); + it('warns on invalid workspace MCP config', async () => { const workspace = await createTempWorkspace(); await mkdir(path.join(workspace, '.vscode'), { recursive: true }); diff --git a/vscode-extension/src/projectStatus.ts b/vscode-extension/src/projectStatus.ts index 0d9454dd8..d7ac4409a 100644 --- a/vscode-extension/src/projectStatus.ts +++ b/vscode-extension/src/projectStatus.ts @@ -1,5 +1,6 @@ import { promises as fs } from 'node:fs'; import * as path from 'node:path'; +import { readUnityMcpProjectConfig } from './unityConfig'; const MCP_SERVER_NAME = 'ai-game-developer'; const UNITY_MCP_PACKAGE_NAME = 'com.ivanmurzak.unity.mcp'; @@ -12,6 +13,7 @@ export interface WorkspaceStatus { unityMarkers: string[]; pluginInstalled: boolean; pluginVersion?: string; + unityMcpProjectConfigExists: boolean; mcpConfigExists: boolean; mcpServerConfigured: boolean; mcpServerTransport?: string; @@ -51,6 +53,14 @@ export async function inspectWorkspaceStatus( const pluginVersion = manifestInfo.dependencies[UNITY_MCP_PACKAGE_NAME]; const pluginInstalled = typeof pluginVersion === 'string' && pluginVersion.length > 0; + const projectConfig = await readUnityMcpProjectConfig(workspacePath); + warnings.push(...projectConfig.warnings); + + if (pluginInstalled && !projectConfig.exists) { + warnings.push( + 'Unity MCP project config is missing. Open Unity once without MCP after installing the plugin so the package can import and initialize.', + ); + } const mcpInfo = await readMcpConfig(mcpConfigPath); warnings.push(...mcpInfo.warnings); @@ -63,6 +73,7 @@ export async function inspectWorkspaceStatus( unityMarkers, pluginInstalled, pluginVersion, + unityMcpProjectConfigExists: projectConfig.exists, mcpConfigExists: mcpInfo.exists, mcpServerConfigured: mcpInfo.hasServerEntry, mcpServerTransport: mcpInfo.transport, @@ -78,6 +89,7 @@ export function formatWorkspaceStatusReport(status: WorkspaceStatus): string { `Unity project detected: ${status.unityProjectDetected ? 'yes' : 'no'}`, `Unity markers: ${status.unityMarkers.length > 0 ? status.unityMarkers.join(', ') : 'none'}`, `Unity MCP plugin installed: ${status.pluginInstalled ? `yes (${status.pluginVersion ?? 'unknown version'})` : 'no'}`, + `Unity MCP project config present: ${status.unityMcpProjectConfigExists ? 'yes' : 'no'}`, `.vscode/mcp.json present: ${status.mcpConfigExists ? 'yes' : 'no'}`, `ai-game-developer configured: ${status.mcpServerConfigured ? 'yes' : 'no'}`, `Configured transport: ${status.mcpServerTransport ?? 'unknown'}`, diff --git a/vscode-extension/src/unityConfig.test.ts b/vscode-extension/src/unityConfig.test.ts new file mode 100644 index 000000000..f650a17ef --- /dev/null +++ b/vscode-extension/src/unityConfig.test.ts @@ -0,0 +1,58 @@ +import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; +import { readUnityMcpProjectConfig } from './unityConfig'; + +const tempRoots: string[] = []; + +afterEach(async () => { + await Promise.all( + tempRoots.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), + ); +}); + +describe('readUnityMcpProjectConfig', () => { + it('reads the Unity MCP project config when present', async () => { + const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, 'UserSettings'), { recursive: true }); + await writeFile( + path.join(workspace, 'UserSettings', 'AI-Game-Developer-Config.json'), + JSON.stringify({ + host: 'http://localhost:6501', + token: 'secret-token', + authOption: 'required', + transportMethod: 'streamableHttp', + keepConnected: true, + }), + ); + + const config = await readUnityMcpProjectConfig(workspace); + + expect(config.exists).toBe(true); + expect(config.host).toBe('http://localhost:6501'); + expect(config.authOption).toBe('required'); + expect(config.transport).toBe('streamableHttp'); + expect(config.keepConnected).toBe(true); + }); + + it('returns a warning when the config is malformed', async () => { + const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, 'UserSettings'), { recursive: true }); + await writeFile( + path.join(workspace, 'UserSettings', 'AI-Game-Developer-Config.json'), + '{broken json', + ); + + const config = await readUnityMcpProjectConfig(workspace); + + expect(config.exists).toBe(true); + expect(config.warnings.some((warning) => warning.includes('Could not parse UserSettings/AI-Game-Developer-Config.json'))).toBe(true); + }); +}); + +async function createTempWorkspace(): Promise { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'unity-mcp-vscode-config-')); + tempRoots.push(tempDir); + return tempDir; +} diff --git a/vscode-extension/src/unityConfig.ts b/vscode-extension/src/unityConfig.ts new file mode 100644 index 000000000..bbe5e4281 --- /dev/null +++ b/vscode-extension/src/unityConfig.ts @@ -0,0 +1,74 @@ +import { promises as fs } from 'node:fs'; +import * as path from 'node:path'; + +export interface UnityMcpProjectConfig { + exists: boolean; + host?: string; + token?: string; + authOption?: 'none' | 'required'; + transport?: 'streamableHttp' | 'stdio'; + keepConnected?: boolean; + warnings: string[]; +} + +export async function readUnityMcpProjectConfig( + workspacePath: string, +): Promise { + const configPath = path.join( + workspacePath, + 'UserSettings', + 'AI-Game-Developer-Config.json', + ); + + if (!(await pathExists(configPath))) { + return { + exists: false, + warnings: [], + }; + } + + try { + const raw = await fs.readFile(configPath, 'utf8'); + const parsed = JSON.parse(raw) as Record; + + return { + exists: true, + host: typeof parsed['host'] === 'string' ? parsed['host'] : undefined, + token: typeof parsed['token'] === 'string' ? parsed['token'] : undefined, + authOption: parseAuthOption(parsed['authOption']), + transport: parseTransport(parsed['transportMethod']), + keepConnected: typeof parsed['keepConnected'] === 'boolean' + ? parsed['keepConnected'] + : undefined, + warnings: [], + }; + } catch (error) { + return { + exists: true, + warnings: [ + `Could not parse UserSettings/AI-Game-Developer-Config.json: ${toErrorMessage(error)}`, + ], + }; + } +} + +async function pathExists(targetPath: string): Promise { + try { + await fs.access(targetPath); + return true; + } catch { + return false; + } +} + +function parseAuthOption(value: unknown): 'none' | 'required' | undefined { + return value === 'none' || value === 'required' ? value : undefined; +} + +function parseTransport(value: unknown): 'streamableHttp' | 'stdio' | undefined { + return value === 'stdio' || value === 'streamableHttp' ? value : undefined; +} + +function toErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} From 96548f03807107a89def7aeb97d184486e6ee123 Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 12:11:52 -0500 Subject: [PATCH 06/12] feat: add guided status recommendations --- vscode-extension/README.md | 12 ++++ vscode-extension/src/extension.ts | 54 +++++++++++++++- vscode-extension/src/projectStatus.test.ts | 15 +++++ vscode-extension/src/projectStatus.ts | 73 ++++++++++++++++++++++ 4 files changed, 153 insertions(+), 1 deletion(-) diff --git a/vscode-extension/README.md b/vscode-extension/README.md index 7a4cfe875..d03325f93 100644 --- a/vscode-extension/README.md +++ b/vscode-extension/README.md @@ -34,6 +34,12 @@ Slice 4 adds: - plain launch or MCP-connected launch - reuse of shared `openProject()` behavior from `unity-mcp-cli` +Slice 5 adds: + +- actionable status recommendations +- guided next-step buttons from `Unity MCP: Check Status` +- clearer first-run setup hints in the output report + ## Local Development ```bash @@ -82,4 +88,10 @@ Then: 19. After Unity imports and creates `UserSettings/AI-Game-Developer-Config.json`, retry `Open Unity With MCP Connection`. 20. Confirm the output channel shows the open-project progress events. +Then: + +21. Run `Unity MCP: Check Status` in projects representing different setup states. +22. Confirm the status report includes `Recommended next actions`. +23. Confirm the status notification offers relevant next-step buttons such as `Install Plugin`, `Configure Project`, `Open Unity`, or `Show Output`. + Unity itself is not required for this slice. diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index cc282c8d5..279446ebe 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -4,6 +4,7 @@ import { ExtensionLogger } from './logging'; import { formatWorkspaceStatusReport, inspectWorkspaceStatus, + type WorkspaceAction, } from './projectStatus'; import { readUnityMcpProjectConfig } from './unityConfig'; import { pickWorkspaceFolder } from './workspace'; @@ -538,13 +539,24 @@ export async function activate(context: vscode.ExtensionContext): Promise ); logger.show(); + const actions = buildStatusMessageActions(status); const summary = status.unityProjectDetected ? `Unity MCP status collected for ${workspaceFolder.name}.` : `${workspaceFolder.name} does not look like a Unity project.`; - void vscode.window.showInformationMessage(summary, 'Show Output').then((selection) => { + void vscode.window.showInformationMessage(summary, ...actions).then(async (selection) => { + if (!selection) { + return; + } + if (selection === 'Show Output') { logger.show(); + return; + } + + const command = statusActionToCommand(selection); + if (command) { + await vscode.commands.executeCommand(command); } }); } catch (error) { @@ -577,6 +589,46 @@ export async function activate(context: vscode.ExtensionContext): Promise }); } +function buildStatusMessageActions(status: { + recommendedActions: WorkspaceAction[]; +}): string[] { + const actions = status.recommendedActions.map((action) => actionToLabel(action)); + actions.push('Show Output'); + return actions.slice(0, 3); +} + +function actionToLabel(action: WorkspaceAction): string { + switch (action) { + case 'trust-workspace': + return 'Manage Trust'; + case 'install-plugin': + return 'Install Plugin'; + case 'open-unity-without-mcp': + return 'Open Unity'; + case 'configure-vscode-mcp': + return 'Configure Project'; + case 'open-unity-with-mcp': + return 'Open Unity With MCP'; + } +} + +function statusActionToCommand(actionLabel: string): string | undefined { + switch (actionLabel) { + case 'Manage Trust': + return 'workbench.trust.manage'; + case 'Install Plugin': + return 'unityMcp.installPlugin'; + case 'Open Unity': + return 'unityMcp.openUnity'; + case 'Configure Project': + return 'unityMcp.configureProject'; + case 'Open Unity With MCP': + return 'unityMcp.openUnity'; + default: + return undefined; + } +} + export function deactivate(): void { // Nothing to dispose beyond the extension context subscriptions. } diff --git a/vscode-extension/src/projectStatus.test.ts b/vscode-extension/src/projectStatus.test.ts index 2b2c3830e..254417c83 100644 --- a/vscode-extension/src/projectStatus.test.ts +++ b/vscode-extension/src/projectStatus.test.ts @@ -45,6 +45,7 @@ describe('inspectWorkspaceStatus', () => { expect(status.pluginVersion).toBe('0.79.0'); expect(status.unityMcpProjectConfigExists).toBe(true); expect(status.warnings).toEqual([]); + expect(status.recommendedActions).toEqual(['configure-vscode-mcp', 'open-unity-with-mcp']); }); it('reports missing Unity markers for a non-Unity workspace', async () => { @@ -56,6 +57,7 @@ describe('inspectWorkspaceStatus', () => { expect(status.unityProjectDetected).toBe(false); expect(status.pluginInstalled).toBe(false); expect(status.unityMarkers).toEqual([]); + expect(status.recommendedActions).toEqual(['trust-workspace']); }); it('warns when the plugin is installed but the Unity MCP project config has not been initialized yet', async () => { @@ -77,10 +79,22 @@ describe('inspectWorkspaceStatus', () => { expect(status.pluginInstalled).toBe(true); expect(status.unityMcpProjectConfigExists).toBe(false); expect(status.warnings.some((warning) => warning.includes('Open Unity once without MCP'))).toBe(true); + expect(status.recommendedActions).toEqual(['open-unity-without-mcp']); }); it('warns on invalid workspace MCP config', async () => { const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, 'Assets'), { recursive: true }); + await mkdir(path.join(workspace, 'ProjectSettings'), { recursive: true }); + await mkdir(path.join(workspace, 'Packages'), { recursive: true }); + await writeFile( + path.join(workspace, 'Packages', 'manifest.json'), + JSON.stringify({ + dependencies: { + 'com.ivanmurzak.unity.mcp': '0.79.0', + }, + }, null, 2), + ); await mkdir(path.join(workspace, '.vscode'), { recursive: true }); await writeFile(path.join(workspace, '.vscode', 'mcp.json'), '{invalid json'); @@ -89,6 +103,7 @@ describe('inspectWorkspaceStatus', () => { expect(status.mcpConfigExists).toBe(true); expect(status.mcpServerConfigured).toBe(false); expect(status.warnings.some((warning) => warning.includes('Could not parse .vscode/mcp.json'))).toBe(true); + expect(status.recommendedActions).toEqual(['open-unity-without-mcp']); }); }); diff --git a/vscode-extension/src/projectStatus.ts b/vscode-extension/src/projectStatus.ts index d7ac4409a..c7c325037 100644 --- a/vscode-extension/src/projectStatus.ts +++ b/vscode-extension/src/projectStatus.ts @@ -18,8 +18,16 @@ export interface WorkspaceStatus { mcpServerConfigured: boolean; mcpServerTransport?: string; warnings: string[]; + recommendedActions: WorkspaceAction[]; } +export type WorkspaceAction = + | 'trust-workspace' + | 'install-plugin' + | 'open-unity-without-mcp' + | 'configure-vscode-mcp' + | 'open-unity-with-mcp'; + export async function inspectWorkspaceStatus( workspacePath: string, workspaceName: string, @@ -64,6 +72,13 @@ export async function inspectWorkspaceStatus( const mcpInfo = await readMcpConfig(mcpConfigPath); warnings.push(...mcpInfo.warnings); + const recommendedActions = buildRecommendedActions({ + trustState, + unityProjectDetected, + pluginInstalled, + unityMcpProjectConfigExists: projectConfig.exists, + mcpServerConfigured: mcpInfo.hasServerEntry, + }); return { workspaceName, @@ -78,6 +93,7 @@ export async function inspectWorkspaceStatus( mcpServerConfigured: mcpInfo.hasServerEntry, mcpServerTransport: mcpInfo.transport, warnings, + recommendedActions, }; } @@ -95,6 +111,13 @@ export function formatWorkspaceStatusReport(status: WorkspaceStatus): string { `Configured transport: ${status.mcpServerTransport ?? 'unknown'}`, ]; + if (status.recommendedActions.length > 0) { + lines.push('Recommended next actions:'); + for (const action of status.recommendedActions) { + lines.push(`- ${describeAction(action)}`); + } + } + if (status.warnings.length > 0) { lines.push('Warnings:'); for (const warning of status.warnings) { @@ -105,6 +128,56 @@ export function formatWorkspaceStatusReport(status: WorkspaceStatus): string { return lines.join('\n'); } +function buildRecommendedActions(input: { + trustState: 'trusted' | 'restricted'; + unityProjectDetected: boolean; + pluginInstalled: boolean; + unityMcpProjectConfigExists: boolean; + mcpServerConfigured: boolean; +}): WorkspaceAction[] { + if (input.trustState !== 'trusted') { + return ['trust-workspace']; + } + + if (!input.unityProjectDetected) { + return []; + } + + const actions: WorkspaceAction[] = []; + + if (!input.pluginInstalled) { + actions.push('install-plugin'); + return actions; + } + + if (!input.unityMcpProjectConfigExists) { + actions.push('open-unity-without-mcp'); + return actions; + } + + if (!input.mcpServerConfigured) { + actions.push('configure-vscode-mcp'); + } + + actions.push('open-unity-with-mcp'); + return actions; +} + +function describeAction(action: WorkspaceAction): string { + switch (action) { + case 'trust-workspace': + return 'Trust this workspace before running Unity MCP write or launch actions.'; + case 'install-plugin': + return 'Run "Unity MCP: Install Plugin".'; + case 'open-unity-without-mcp': + return 'Run "Unity MCP: Open Unity" and choose "Open Unity" once so the package can initialize.'; + case 'configure-vscode-mcp': + return 'Run "Unity MCP: Configure Project".'; + case 'open-unity-with-mcp': + return 'Run "Unity MCP: Open Unity" and choose "Open Unity With MCP Connection".'; + } +} + interface PackageManifestInfo { exists: boolean; dependencies: Record; From 88b86199ecc7010270111509b5753e54d904294c Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 13:03:34 -0500 Subject: [PATCH 07/12] feat: add vscode dashboard ui --- vscode-extension/media/unity-mcp-activity.svg | 5 + vscode-extension/package.json | 46 +- vscode-extension/src/dashboard.test.ts | 122 ++++ vscode-extension/src/dashboard.ts | 587 ++++++++++++++++++ vscode-extension/src/extension.ts | 362 +++++++---- vscode-extension/src/workspace.ts | 26 +- 6 files changed, 1025 insertions(+), 123 deletions(-) create mode 100644 vscode-extension/media/unity-mcp-activity.svg create mode 100644 vscode-extension/src/dashboard.test.ts create mode 100644 vscode-extension/src/dashboard.ts diff --git a/vscode-extension/media/unity-mcp-activity.svg b/vscode-extension/media/unity-mcp-activity.svg new file mode 100644 index 000000000..36a9b7f31 --- /dev/null +++ b/vscode-extension/media/unity-mcp-activity.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 5d62f7336..dce20f9a6 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -21,11 +21,15 @@ "workspace" ], "activationEvents": [ + "onStartupFinished", + "onView:unityMcp.dashboard", "onCommand:unityMcp.checkStatus", "onCommand:unityMcp.showOutput", "onCommand:unityMcp.configureProject", "onCommand:unityMcp.installPlugin", - "onCommand:unityMcp.openUnity" + "onCommand:unityMcp.openUnity", + "onCommand:unityMcp.showDashboard", + "onCommand:unityMcp.refreshDashboard" ], "capabilities": { "untrustedWorkspaces": { @@ -51,11 +55,51 @@ "command": "unityMcp.openUnity", "title": "Unity MCP: Open Unity" }, + { + "command": "unityMcp.showDashboard", + "title": "Unity MCP: Show Dashboard" + }, + { + "command": "unityMcp.refreshDashboard", + "title": "Unity MCP: Refresh Dashboard" + }, { "command": "unityMcp.showOutput", "title": "Unity MCP: Show Output" } ], + "viewsContainers": { + "activitybar": [ + { + "id": "unityMcpSidebar", + "title": "Unity MCP", + "icon": "media/unity-mcp-activity.svg" + } + ] + }, + "views": { + "unityMcpSidebar": [ + { + "id": "unityMcp.dashboard", + "name": "Workspace", + "type": "webview" + } + ] + }, + "menus": { + "view/title": [ + { + "command": "unityMcp.refreshDashboard", + "when": "view == unityMcp.dashboard", + "group": "navigation" + }, + { + "command": "unityMcp.showOutput", + "when": "view == unityMcp.dashboard", + "group": "navigation" + } + ] + }, "configuration": { "title": "Unity MCP", "properties": { diff --git a/vscode-extension/src/dashboard.test.ts b/vscode-extension/src/dashboard.test.ts new file mode 100644 index 000000000..85b48c0b3 --- /dev/null +++ b/vscode-extension/src/dashboard.test.ts @@ -0,0 +1,122 @@ +import type * as vscode from 'vscode'; +import { describe, expect, it, vi } from 'vitest'; +import { + buildDashboardActions, + buildStatusBarPresentation, + type DashboardSnapshot, +} from './dashboard'; +import type { WorkspaceStatus } from './projectStatus'; + +vi.mock('vscode', () => ({})); + +describe('buildStatusBarPresentation', () => { + it('shows install state when the Unity MCP plugin is missing', () => { + const presentation = buildStatusBarPresentation( + createSnapshot({ + pluginInstalled: false, + unityMcpProjectConfigExists: false, + mcpServerConfigured: false, + }), + ); + + expect(presentation.text).toBe('$(package) Unity MCP: Install'); + }); + + it('shows initialization state when the plugin is installed but the Unity config is missing', () => { + const presentation = buildStatusBarPresentation( + createSnapshot({ + pluginInstalled: true, + unityMcpProjectConfigExists: false, + mcpServerConfigured: false, + }), + ); + + expect(presentation.text).toBe('$(sync~spin) Unity MCP: Init'); + }); + + it('shows ready state when the workspace is fully configured', () => { + const presentation = buildStatusBarPresentation( + createSnapshot({ + pluginInstalled: true, + unityMcpProjectConfigExists: true, + mcpServerConfigured: true, + }), + ); + + expect(presentation.text).toBe('$(check) Unity MCP: Ready'); + }); +}); + +describe('buildDashboardActions', () => { + it('prioritizes trust management for restricted workspaces', () => { + const actions = buildDashboardActions( + createSnapshot({ + trustState: 'restricted', + recommendedActions: ['trust-workspace'], + }), + ); + + expect(actions.map((action) => action.commandId)).toEqual([ + 'workbench.trust.manage', + 'unityMcp.checkStatus', + 'unityMcp.showOutput', + ]); + }); + + it('prioritizes connected launch for ready projects', () => { + const actions = buildDashboardActions( + createSnapshot({ + pluginInstalled: true, + unityMcpProjectConfigExists: true, + mcpServerConfigured: true, + recommendedActions: ['open-unity-with-mcp'], + }), + ); + + expect(actions[0]?.commandId).toBe('unityMcp.openUnityConnected'); + expect(actions.some((action) => action.commandId === 'unityMcp.openUnityPlain')).toBe(true); + }); + + it('prioritizes configuration when VS Code MCP is not configured yet', () => { + const actions = buildDashboardActions( + createSnapshot({ + pluginInstalled: true, + unityMcpProjectConfigExists: true, + mcpServerConfigured: false, + recommendedActions: ['configure-vscode-mcp', 'open-unity-with-mcp'], + }), + ); + + expect(actions[0]?.commandId).toBe('unityMcp.configureProject'); + }); +}); + +function createSnapshot(overrides: Partial): DashboardSnapshot { + return { + workspaceFolder: { + name: 'TestProject', + index: 0, + uri: { fsPath: '/tmp/TestProject' } as vscode.Uri, + } as vscode.WorkspaceFolder, + status: createStatus(overrides), + }; +} + +function createStatus(overrides: Partial): WorkspaceStatus { + return { + workspaceName: 'TestProject', + workspacePath: '/tmp/TestProject', + trustState: 'trusted', + unityProjectDetected: true, + unityMarkers: ['Assets/', 'ProjectSettings/', 'Packages/manifest.json'], + pluginInstalled: true, + pluginVersion: '0.79.0', + unityMcpProjectConfigExists: true, + mcpConfigExists: true, + mcpServerConfigured: true, + mcpServerTransport: 'http', + warnings: [], + recommendedActions: ['open-unity-with-mcp'], + ...overrides, + }; +} diff --git a/vscode-extension/src/dashboard.ts b/vscode-extension/src/dashboard.ts new file mode 100644 index 000000000..b6c3d0a24 --- /dev/null +++ b/vscode-extension/src/dashboard.ts @@ -0,0 +1,587 @@ +import * as vscode from 'vscode'; +import { + formatWorkspaceStatusReport, + type WorkspaceStatus, +} from './projectStatus'; + +export interface DashboardSnapshot { + workspaceFolder?: vscode.WorkspaceFolder; + status?: WorkspaceStatus; +} + +interface DashboardActionItem { + commandId: string; + label: string; + description: string; + recommended: boolean; +} + +interface DashboardRecommendation { + title: string; + detail: string; +} + +export class UnityMcpDashboardProvider implements vscode.WebviewViewProvider { + public static readonly viewId = 'unityMcp.dashboard'; + + private view?: vscode.WebviewView; + + public constructor( + private readonly extensionUri: vscode.Uri, + private readonly getSnapshot: () => Promise, + private readonly onCommand: (commandId: string) => Promise, + private readonly onEvent?: (event: string, properties?: Record) => void, + ) {} + + public resolveWebviewView( + webviewView: vscode.WebviewView, + ): void | Thenable { + this.view = webviewView; + this.onEvent?.('dashboard:viewResolved', { + viewId: UnityMcpDashboardProvider.viewId, + }); + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [vscode.Uri.joinPath(this.extensionUri, 'media')], + }; + + webviewView.webview.onDidReceiveMessage(async (message: unknown) => { + if ( + typeof message === 'object' && + message !== null && + 'type' in message && + 'commandId' in message && + message.type === 'run-command' && + typeof message.commandId === 'string' + ) { + this.onEvent?.('dashboard:command', { + commandId: message.commandId, + }); + await this.onCommand(message.commandId); + } + }); + + return this.refresh(); + } + + public hasResolvedView(): boolean { + return this.view !== undefined; + } + + public async refresh(): Promise { + if (!this.view) { + this.onEvent?.('dashboard:refreshSkipped', { + reason: 'view-not-resolved', + }); + return; + } + + const snapshot = await this.getSnapshot(); + this.onEvent?.('dashboard:render', { + hasWorkspace: Boolean(snapshot.workspaceFolder), + workspaceName: snapshot.workspaceFolder?.name, + trustState: snapshot.status?.trustState, + unityProjectDetected: snapshot.status?.unityProjectDetected, + pluginInstalled: snapshot.status?.pluginInstalled, + unityConfigReady: snapshot.status?.unityMcpProjectConfigExists, + mcpConfigured: snapshot.status?.mcpServerConfigured, + }); + this.view.webview.html = renderDashboardHtml( + this.view.webview, + this.extensionUri, + snapshot, + ); + } +} + +export function buildStatusBarPresentation(snapshot: DashboardSnapshot): { + text: string; + tooltip: string; +} { + const status = snapshot.status; + + if (!snapshot.workspaceFolder || !status) { + return { + text: '$(circle-slash) Unity MCP', + tooltip: 'Open a workspace folder to use Unity MCP.', + }; + } + + if (status.trustState !== 'trusted') { + return { + text: '$(shield) Unity MCP: Trust', + tooltip: 'Trust this workspace before using Unity MCP write or launch actions.', + }; + } + + if (!status.unityProjectDetected) { + return { + text: '$(circle-slash) Unity MCP', + tooltip: `${status.workspaceName} does not look like a Unity project.`, + }; + } + + if (!status.pluginInstalled) { + return { + text: '$(package) Unity MCP: Install', + tooltip: 'Unity MCP plugin is missing. Install it into this Unity project.', + }; + } + + if (!status.unityMcpProjectConfigExists) { + return { + text: '$(sync~spin) Unity MCP: Init', + tooltip: 'Open Unity once without MCP so the Unity MCP package can initialize.', + }; + } + + if (!status.mcpServerConfigured) { + return { + text: '$(settings-gear) Unity MCP: Configure', + tooltip: 'VS Code MCP config is missing or incomplete. Configure this project.', + }; + } + + return { + text: '$(check) Unity MCP: Ready', + tooltip: `Unity MCP is ready for ${status.workspaceName}.`, + }; +} + +export function buildDashboardActions(snapshot: DashboardSnapshot): DashboardActionItem[] { + const status = snapshot.status; + + if (!snapshot.workspaceFolder || !status) { + return [ + { + commandId: 'unityMcp.checkStatus', + label: 'Check Status', + description: 'Inspect the currently open workspace.', + recommended: true, + }, + { + commandId: 'unityMcp.showOutput', + label: 'Show Output', + description: 'Open the Unity MCP output channel.', + recommended: false, + }, + ]; + } + + if (status.trustState !== 'trusted') { + return [ + { + commandId: 'workbench.trust.manage', + label: 'Manage Trust', + description: 'Trust this workspace before running write or launch actions.', + recommended: true, + }, + { + commandId: 'unityMcp.checkStatus', + label: 'Check Status', + description: 'Refresh workspace diagnostics and next-step guidance.', + recommended: false, + }, + { + commandId: 'unityMcp.showOutput', + label: 'Show Output', + description: 'Open the Unity MCP output channel for logs and diagnostics.', + recommended: false, + }, + ]; + } + + if (!status.unityProjectDetected) { + return [ + { + commandId: 'unityMcp.checkStatus', + label: 'Check Status', + description: 'Refresh workspace diagnostics and confirm Unity project detection.', + recommended: true, + }, + { + commandId: 'unityMcp.showOutput', + label: 'Show Output', + description: 'Open the Unity MCP output channel for logs and diagnostics.', + recommended: false, + }, + ]; + } + + const items: DashboardActionItem[] = []; + + if (!status.pluginInstalled) { + items.push({ + commandId: 'unityMcp.installPlugin', + label: 'Install Plugin', + description: 'Add the Unity MCP package to Packages/manifest.json.', + recommended: true, + }); + items.push({ + commandId: 'unityMcp.configureProject', + label: 'Configure Project', + description: 'Write .vscode/mcp.json now if you want VS Code ready before the plugin install finishes.', + recommended: false, + }); + } else if (!status.unityMcpProjectConfigExists) { + items.push({ + commandId: 'unityMcp.openUnityPlain', + label: 'Open Unity', + description: 'Launch Unity once so the plugin can import and create its project config.', + recommended: true, + }); + items.push({ + commandId: 'unityMcp.installPlugin', + label: 'Install Plugin', + description: 'Re-run package installation to reconcile Packages/manifest.json if needed.', + recommended: false, + }); + } else if (!status.mcpServerConfigured) { + items.push({ + commandId: 'unityMcp.configureProject', + label: 'Configure Project', + description: 'Write or update .vscode/mcp.json for this Unity project.', + recommended: true, + }); + items.push({ + commandId: 'unityMcp.openUnityConnected', + label: 'Open Unity With MCP', + description: 'Launch Unity using the current AI-Game-Developer connection config.', + recommended: false, + }); + } else { + items.push({ + commandId: 'unityMcp.openUnityConnected', + label: 'Open Unity With MCP', + description: 'Launch Unity using the current AI-Game-Developer connection config.', + recommended: true, + }); + items.push({ + commandId: 'unityMcp.openUnityPlain', + label: 'Open Unity', + description: 'Launch Unity without overriding MCP connection settings.', + recommended: false, + }); + } + + if (status.pluginInstalled) { + items.push({ + commandId: 'unityMcp.configureProject', + label: 'Configure Project', + description: 'Write or update .vscode/mcp.json for this Unity project.', + recommended: false, + }); + } + + items.push({ + commandId: 'unityMcp.checkStatus', + label: 'Check Status', + description: 'Refresh workspace diagnostics and next-step guidance.', + recommended: false, + }); + items.push({ + commandId: 'unityMcp.showOutput', + label: 'Show Output', + description: 'Open the Unity MCP output channel for logs and diagnostics.', + recommended: false, + }); + + return dedupeActions(items); +} + +function renderDashboardHtml( + webview: vscode.Webview, + extensionUri: vscode.Uri, + snapshot: DashboardSnapshot, +): string { + const nonce = createNonce(); + const iconUri = webview.asWebviewUri( + vscode.Uri.joinPath(extensionUri, 'media', 'unity-mcp-activity.svg'), + ); + const actions = buildDashboardActions(snapshot); + const status = snapshot.status; + const nextStep = buildDashboardRecommendation(status); + const report = status + ? formatWorkspaceStatusReport(status) + : 'Open a workspace folder to begin using Unity MCP.'; + + return ` + + + + + + Unity MCP + + + +
+ +
+

Unity MCP

+

${snapshot.workspaceFolder ? escapeHtml(snapshot.workspaceFolder.name) : 'No workspace selected'}

+
+
+ + ${ + nextStep + ? `
+

Next Step

+

${escapeHtml(nextStep.title)}

+

${escapeHtml(nextStep.detail)}

+
` + : '' + } + +
+

Workspace Status

+ ${ + status + ? `
+
+ Workspace Trust + ${escapeHtml(status.trustState)} +
+
+ Unity Project + ${status.unityProjectDetected ? 'Detected' : 'Not detected'} +
+
+ Plugin + ${status.pluginInstalled ? `Installed (${escapeHtml(status.pluginVersion ?? 'unknown')})` : 'Missing'} +
+
+ Unity Config + ${status.unityMcpProjectConfigExists ? 'Ready' : 'Missing'} +
+
+ VS Code MCP + ${status.mcpServerConfigured ? `Configured (${escapeHtml(status.mcpServerTransport ?? 'unknown')})` : 'Not configured'} +
+
` + : `

Open a Unity project folder to see setup status and actions.

` + } +
+ +
+

Actions

+
+ ${actions.map((action) => ` + + `).join('')} +
+
+ +
+

Diagnostics

+
${escapeHtml(report)}
+
+ + + +`; +} + +function buildDashboardRecommendation( + status: WorkspaceStatus | undefined, +): DashboardRecommendation | undefined { + if (!status) { + return { + title: 'Open a Unity project', + detail: 'Select a workspace folder so Unity MCP can inspect the project and suggest setup steps.', + }; + } + + const [nextAction] = status.recommendedActions; + if (!nextAction) { + return { + title: 'Unity MCP is ready', + detail: 'Use the dashboard or status bar to open Unity, reconfigure MCP transport, or inspect logs.', + }; + } + + switch (nextAction) { + case 'trust-workspace': + return { + title: 'Trust this workspace', + detail: 'Unity MCP only enables write and launch actions after VS Code trusts the current workspace.', + }; + case 'install-plugin': + return { + title: 'Install the Unity MCP plugin', + detail: 'Add the Unity package to Packages/manifest.json before trying to launch or connect with MCP.', + }; + case 'open-unity-without-mcp': + return { + title: 'Initialize the Unity project', + detail: 'Open Unity once without MCP so the newly installed package can import and create its project config.', + }; + case 'configure-vscode-mcp': + return { + title: 'Configure VS Code MCP', + detail: 'Write or update .vscode/mcp.json so VS Code can connect to the Unity MCP server for this project.', + }; + case 'open-unity-with-mcp': + return { + title: 'Open Unity with MCP', + detail: 'The project looks ready. Launch Unity with the saved MCP connection settings.', + }; + } +} + +function dedupeActions(items: DashboardActionItem[]): DashboardActionItem[] { + const seen = new Set(); + const deduped: DashboardActionItem[] = []; + + for (const item of items) { + if (seen.has(item.commandId)) { + continue; + } + + seen.add(item.commandId); + deduped.push(item); + } + + return deduped; +} + +function createNonce(): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let index = 0; index < 24; index += 1) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +function escapeHtml(value: string): string { + return value + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); +} diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index 279446ebe..18960eb11 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -1,5 +1,10 @@ import * as vscode from 'vscode'; import { configureVscodeProject, installUnityMcpPlugin, openUnityProject } from './cliAdapter'; +import { + buildStatusBarPresentation, + type DashboardSnapshot, + UnityMcpDashboardProvider, +} from './dashboard'; import { ExtensionLogger } from './logging'; import { formatWorkspaceStatusReport, @@ -7,89 +12,137 @@ import { type WorkspaceAction, } from './projectStatus'; import { readUnityMcpProjectConfig } from './unityConfig'; -import { pickWorkspaceFolder } from './workspace'; +import { getPreferredWorkspaceFolder, pickWorkspaceFolder } from './workspace'; export async function activate(context: vscode.ExtensionContext): Promise { const logger = new ExtensionLogger(); context.subscriptions.push(logger); + const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); + statusBarItem.command = 'unityMcp.showDashboard'; + context.subscriptions.push(statusBarItem); + + const dashboardProvider = new UnityMcpDashboardProvider( + context.extensionUri, + getDashboardSnapshot, + async (commandId) => { + await vscode.commands.executeCommand(commandId); + await refreshUi(); + }, + (event, properties) => { + logger.info(event, properties ?? {}); + }, + ); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + UnityMcpDashboardProvider.viewId, + dashboardProvider, + ), + ); logger.info('activate:start', { workspaceCount: vscode.workspace.workspaceFolders?.length ?? 0, trusted: vscode.workspace.isTrusted, }); - context.subscriptions.push( - vscode.workspace.onDidGrantWorkspaceTrust(() => { - logger.info('trust:granted', {}); - }), - ); + async function getDashboardSnapshot(): Promise { + const workspaceFolder = getPreferredWorkspaceFolder(); + if (!workspaceFolder) { + return {}; + } - context.subscriptions.push( - vscode.commands.registerCommand('unityMcp.showOutput', () => { - logger.show(); - logger.debug('output:show', {}); - }), - ); + return { + workspaceFolder, + status: await inspectWorkspaceStatus( + workspaceFolder.uri.fsPath, + workspaceFolder.name, + vscode.workspace.isTrusted ? 'trusted' : 'restricted', + ), + }; + } - context.subscriptions.push( - vscode.commands.registerCommand('unityMcp.openUnity', async () => { - const workspaceFolder = await pickWorkspaceFolder(); - logger.debug('workspace:pick', { - selected: workspaceFolder?.uri.fsPath ?? null, - }); + async function refreshUi(): Promise { + logger.debug('dashboard:refreshUiStart', { + viewResolved: dashboardProvider.hasResolvedView(), + }); + const snapshot = await getDashboardSnapshot(); + logger.debug('dashboard:snapshot', { + hasWorkspace: Boolean(snapshot.workspaceFolder), + workspaceName: snapshot.workspaceFolder?.name, + trustState: snapshot.status?.trustState, + unityProjectDetected: snapshot.status?.unityProjectDetected, + pluginInstalled: snapshot.status?.pluginInstalled, + unityConfigReady: snapshot.status?.unityMcpProjectConfigExists, + mcpConfigured: snapshot.status?.mcpServerConfigured, + }); + const statusBar = buildStatusBarPresentation(snapshot); + statusBarItem.text = statusBar.text; + statusBarItem.tooltip = statusBar.tooltip; + statusBarItem.show(); + await dashboardProvider.refresh(); + } - if (!workspaceFolder) { - void vscode.window.showWarningMessage( - 'Unity MCP needs an open workspace folder before it can launch Unity.', - ); - logger.warn('openUnity:error', { - reason: 'no-workspace-folder', - }); - return; - } + async function runOpenUnity( + mode: 'prompt' | 'plain' | 'connected', + ): Promise { + const workspaceFolder = await pickWorkspaceFolder(); + logger.debug('workspace:pick', { + selected: workspaceFolder?.uri.fsPath ?? null, + }); + + if (!workspaceFolder) { + void vscode.window.showWarningMessage( + 'Unity MCP needs an open workspace folder before it can launch Unity.', + ); + logger.warn('openUnity:error', { + reason: 'no-workspace-folder', + }); + return; + } - if (!vscode.workspace.isTrusted) { - logger.warn('openUnity:precheck', { - workspace: workspaceFolder.uri.fsPath, - reason: 'workspace-not-trusted', - }); + if (!vscode.workspace.isTrusted) { + logger.warn('openUnity:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'workspace-not-trusted', + }); - const selection = await vscode.window.showWarningMessage( - 'Unity MCP only launches Unity from a trusted workspace.', - 'Manage Trust', - ); + const selection = await vscode.window.showWarningMessage( + 'Unity MCP only launches Unity from a trusted workspace.', + 'Manage Trust', + ); - if (selection === 'Manage Trust') { - await vscode.commands.executeCommand('workbench.trust.manage'); - } - return; + if (selection === 'Manage Trust') { + await vscode.commands.executeCommand('workbench.trust.manage'); } + return; + } - const initialStatus = await inspectWorkspaceStatus( - workspaceFolder.uri.fsPath, - workspaceFolder.name, - 'trusted', - ); + const initialStatus = await inspectWorkspaceStatus( + workspaceFolder.uri.fsPath, + workspaceFolder.name, + 'trusted', + ); - if (!initialStatus.unityProjectDetected) { - logger.warn('openUnity:precheck', { - workspace: workspaceFolder.uri.fsPath, - reason: 'not-unity-project', - }); + if (!initialStatus.unityProjectDetected) { + logger.warn('openUnity:precheck', { + workspace: workspaceFolder.uri.fsPath, + reason: 'not-unity-project', + }); - void vscode.window.showErrorMessage( - 'Unity MCP can only open a workspace that looks like a Unity project.', - ); - return; - } + void vscode.window.showErrorMessage( + 'Unity MCP can only open a workspace that looks like a Unity project.', + ); + return; + } - const projectConfig = await readUnityMcpProjectConfig(workspaceFolder.uri.fsPath); - if (projectConfig.warnings.length > 0) { - logger.warn('openUnity:configWarnings', { - warnings: projectConfig.warnings, - }); - } + const projectConfig = await readUnityMcpProjectConfig(workspaceFolder.uri.fsPath); + if (projectConfig.warnings.length > 0) { + logger.warn('openUnity:configWarnings', { + warnings: projectConfig.warnings, + }); + } + let effectiveMode = mode; + if (effectiveMode === 'prompt') { const openMode = await vscode.window.showQuickPick( [ { @@ -118,78 +171,137 @@ export async function activate(context: vscode.ExtensionContext): Promise return; } - let effectiveMode = openMode.mode; - if (effectiveMode === 'connected' && !projectConfig.exists) { - logger.warn('openUnity:precheck', { - workspace: workspaceFolder.uri.fsPath, - reason: 'project-config-missing', - }); - - const selection = await vscode.window.showWarningMessage( - 'Unity MCP is installed, but the project has not finished first-time initialization yet. Open Unity once without MCP so the package can import and create its project config, then retry connected launch.', - 'Open Without MCP', - 'Show Output', - 'Cancel', - ); - - if (selection === 'Show Output') { - logger.show(); - } - - if (selection !== 'Open Without MCP') { - return; - } - - effectiveMode = 'plain'; - } + effectiveMode = openMode.mode; + } - logger.info('openUnity:start', { + if (effectiveMode === 'connected' && !projectConfig.exists) { + logger.warn('openUnity:precheck', { workspace: workspaceFolder.uri.fsPath, - mode: effectiveMode, + reason: 'project-config-missing', }); - const result = await openUnityProject(logger, { - workspacePath: workspaceFolder.uri.fsPath, - noConnect: effectiveMode === 'plain', - url: effectiveMode === 'connected' ? projectConfig.host : undefined, - token: effectiveMode === 'connected' ? projectConfig.token : undefined, - auth: effectiveMode === 'connected' ? projectConfig.authOption : undefined, - keepConnected: effectiveMode === 'connected' ? projectConfig.keepConnected : undefined, - transport: effectiveMode === 'connected' ? projectConfig.transport : undefined, - startServer: effectiveMode === 'connected' ? true : undefined, - }); + const selection = await vscode.window.showWarningMessage( + 'Unity MCP is installed, but the project has not finished first-time initialization yet. Open Unity once without MCP so the package can import and create its project config, then retry connected launch.', + 'Open Without MCP', + 'Show Output', + 'Cancel', + ); - if (result.kind === 'failure') { - void vscode.window.showErrorMessage( - `Unity MCP could not open Unity: ${result.errorMessage}`, - 'Show Output', - ).then((selection) => { - if (selection === 'Show Output') { - logger.show(); - } - }); - return; + if (selection === 'Show Output') { + logger.show(); } - if (result.warnings.length > 0) { - logger.warn('openUnity:warnings', { - warnings: result.warnings, - }); + if (selection !== 'Open Without MCP') { + return; } - logger.show(); - const summary = result.alreadyRunning - ? `Unity is already running for ${workspaceFolder.name}.` - : `Unity launch requested for ${workspaceFolder.name}.`; - - void vscode.window.showInformationMessage( - summary, + effectiveMode = 'plain'; + } + + logger.info('openUnity:start', { + workspace: workspaceFolder.uri.fsPath, + mode: effectiveMode, + }); + + const result = await openUnityProject(logger, { + workspacePath: workspaceFolder.uri.fsPath, + noConnect: effectiveMode === 'plain', + url: effectiveMode === 'connected' ? projectConfig.host : undefined, + token: effectiveMode === 'connected' ? projectConfig.token : undefined, + auth: effectiveMode === 'connected' ? projectConfig.authOption : undefined, + keepConnected: effectiveMode === 'connected' ? projectConfig.keepConnected : undefined, + transport: effectiveMode === 'connected' ? projectConfig.transport : undefined, + startServer: effectiveMode === 'connected' ? true : undefined, + }); + + if (result.kind === 'failure') { + void vscode.window.showErrorMessage( + `Unity MCP could not open Unity: ${result.errorMessage}`, 'Show Output', ).then((selection) => { if (selection === 'Show Output') { logger.show(); } }); + return; + } + + if (result.warnings.length > 0) { + logger.warn('openUnity:warnings', { + warnings: result.warnings, + }); + } + + logger.show(); + const summary = result.alreadyRunning + ? `Unity is already running for ${workspaceFolder.name}.` + : `Unity launch requested for ${workspaceFolder.name}.`; + + void vscode.window.showInformationMessage( + summary, + 'Show Output', + ).then((selection) => { + if (selection === 'Show Output') { + logger.show(); + } + }); + } + + context.subscriptions.push( + vscode.workspace.onDidGrantWorkspaceTrust(() => { + logger.info('trust:granted', {}); + void refreshUi(); + }), + ); + + context.subscriptions.push( + vscode.workspace.onDidChangeWorkspaceFolders(() => { + void refreshUi(); + }), + ); + + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(() => { + void refreshUi(); + }), + ); + + context.subscriptions.push( + vscode.workspace.onDidSaveTextDocument(() => { + void refreshUi(); + }), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.showOutput', () => { + logger.show(); + logger.debug('output:show', {}); + }), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.openUnity', async () => runOpenUnity('prompt')), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.openUnityPlain', async () => runOpenUnity('plain')), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.openUnityConnected', async () => runOpenUnity('connected')), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.showDashboard', async () => { + await vscode.commands.executeCommand('workbench.view.extension.unityMcpSidebar'); + await refreshUi(); + }), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('unityMcp.refreshDashboard', async () => { + await refreshUi(); + logger.info('dashboard:refreshed', {}); }), ); @@ -331,6 +443,8 @@ export async function activate(context: vscode.ExtensionContext): Promise logger.show(); } }); + + await refreshUi(); }), ); @@ -491,6 +605,8 @@ export async function activate(context: vscode.ExtensionContext): Promise logger.show(); } }); + + await refreshUi(); }), ); @@ -557,8 +673,10 @@ export async function activate(context: vscode.ExtensionContext): Promise const command = statusActionToCommand(selection); if (command) { await vscode.commands.executeCommand(command); + await refreshUi(); } }); + await refreshUi(); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('status:error', { @@ -584,9 +702,13 @@ export async function activate(context: vscode.ExtensionContext): Promise 'unityMcp.configureProject', 'unityMcp.installPlugin', 'unityMcp.openUnity', + 'unityMcp.showDashboard', + 'unityMcp.refreshDashboard', 'unityMcp.showOutput', ], }); + + await refreshUi(); } function buildStatusMessageActions(status: { @@ -619,11 +741,11 @@ function statusActionToCommand(actionLabel: string): string | undefined { case 'Install Plugin': return 'unityMcp.installPlugin'; case 'Open Unity': - return 'unityMcp.openUnity'; + return 'unityMcp.openUnityPlain'; case 'Configure Project': return 'unityMcp.configureProject'; case 'Open Unity With MCP': - return 'unityMcp.openUnity'; + return 'unityMcp.openUnityConnected'; default: return undefined; } diff --git a/vscode-extension/src/workspace.ts b/vscode-extension/src/workspace.ts index 58e3b52c7..b1b39ac4e 100644 --- a/vscode-extension/src/workspace.ts +++ b/vscode-extension/src/workspace.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; -export async function pickWorkspaceFolder(): Promise { +export function getPreferredWorkspaceFolder(): vscode.WorkspaceFolder | undefined { const folders = vscode.workspace.workspaceFolders; if (!folders || folders.length === 0) { @@ -19,8 +19,30 @@ export async function pickWorkspaceFolder(): Promise { + const folders = vscode.workspace.workspaceFolders; + + if (!folders || folders.length === 0) { + return undefined; + } + + if (folders.length === 1) { + return folders[0]; + } + + const preferredFolder = getPreferredWorkspaceFolder(); + const orderedFolders = preferredFolder + ? [ + preferredFolder, + ...folders.filter((folder) => folder.uri.toString() !== preferredFolder.uri.toString()), + ] + : folders; + return vscode.window.showQuickPick( - folders.map((folder) => ({ + orderedFolders.map((folder) => ({ label: folder.name, description: folder.uri.fsPath, folder, From 136cfc3957b605b0f7b1c9e2302718283cc0558d Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 13:26:49 -0500 Subject: [PATCH 08/12] chore: prepare vscode extension release handoff --- vscode-extension/.vscodeignore | 10 + vscode-extension/CHANGELOG.md | 10 + vscode-extension/LICENSE.txt | 176 + vscode-extension/PUBLISHING.md | 42 + vscode-extension/README.md | 142 +- vscode-extension/SUPPORT.md | 22 + vscode-extension/media/marketplace-icon.png | Bin 0 -> 12553 bytes vscode-extension/package-lock.json | 4552 +++++++++++++++++-- vscode-extension/package.json | 21 +- 9 files changed, 4515 insertions(+), 460 deletions(-) create mode 100644 vscode-extension/.vscodeignore create mode 100644 vscode-extension/CHANGELOG.md create mode 100644 vscode-extension/LICENSE.txt create mode 100644 vscode-extension/PUBLISHING.md create mode 100644 vscode-extension/SUPPORT.md create mode 100644 vscode-extension/media/marketplace-icon.png diff --git a/vscode-extension/.vscodeignore b/vscode-extension/.vscodeignore new file mode 100644 index 000000000..a71abd56a --- /dev/null +++ b/vscode-extension/.vscodeignore @@ -0,0 +1,10 @@ +.vscode/** +src/** +tsconfig.json +.gitignore +package-lock.json +vitest.config.* +**/*.test.* +.DS_Store +*.vsix +PUBLISHING.md diff --git a/vscode-extension/CHANGELOG.md b/vscode-extension/CHANGELOG.md new file mode 100644 index 000000000..9f277d530 --- /dev/null +++ b/vscode-extension/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +## 0.0.1 + +- Added Unity MCP workspace diagnostics and output logging. +- Added guided project configuration for `.vscode/mcp.json`. +- Added Unity MCP plugin installation from VS Code. +- Added Unity launch commands for plain and MCP-connected flows. +- Added setup recommendations in status reporting. +- Added Activity Bar dashboard and status bar integration. diff --git a/vscode-extension/LICENSE.txt b/vscode-extension/LICENSE.txt new file mode 100644 index 000000000..106983b4c --- /dev/null +++ b/vscode-extension/LICENSE.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + Copyright (c) 2025 Ivan Murzak + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/vscode-extension/PUBLISHING.md b/vscode-extension/PUBLISHING.md new file mode 100644 index 000000000..fdc672bf6 --- /dev/null +++ b/vscode-extension/PUBLISHING.md @@ -0,0 +1,42 @@ +# Publishing Handoff + +This extension is prepared for local VSIX packaging, but it is intentionally not configured for publishing from a personal account. + +## Before Publishing + +1. Replace the temporary `publisher` value in [package.json](/Users/suporte/Unity-MCP/vscode-extension/package.json) with the official Marketplace publisher id. +2. Confirm the extension `name` and `displayName` are acceptable and unique for the Marketplace listing. +3. Review the Marketplace icon in `media/marketplace-icon.png`. +4. Review `README.md`, `CHANGELOG.md`, and `SUPPORT.md` for the final public wording. +5. Decide whether the first Marketplace release should remain marked as preview. + +## Verification + +Run from `/Users/suporte/Unity-MCP/vscode-extension`: + +```bash +npm install +npm run build +npm test +npm run package:vsix +``` + +Then install the generated VSIX in a normal VS Code window and verify: + +- Activity Bar dashboard loads +- status bar item appears +- `Check Status` works +- `Install Plugin` works +- `Configure Project` works +- `Open Unity` works + +## Publish + +Use the official publisher account and the VS Code publishing tooling to publish from the extension folder. The official documentation is: + +- [Publishing Extensions](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) + +## Notes + +- The package metadata already includes `README.md`, `CHANGELOG.md`, `LICENSE.txt`, `SUPPORT.md`, and a PNG icon for Marketplace readiness. +- The current `publisher` value is only a placeholder for local packaging and should not be used for the real release. diff --git a/vscode-extension/README.md b/vscode-extension/README.md index d03325f93..53b6af36d 100644 --- a/vscode-extension/README.md +++ b/vscode-extension/README.md @@ -2,43 +2,27 @@ This package is the isolated VS Code extension workspace for GitHub issue `#613`. -## Current Slice +It is currently a preview extension focused on local project setup and diagnostics for Unity MCP. It does not publish telemetry and it only performs file mutations or Unity launch actions after explicit user action in a trusted workspace. -Slice 1 adds: +## Current Capabilities -- extension scaffold -- output channel logging -- Workspace Trust awareness -- Unity project detection -- Unity MCP plugin detection -- `.vscode/mcp.json` detection +- `Unity MCP` Activity Bar dashboard with: + - current workspace setup state + - recommended next step + - action buttons for install, configure, launch, refresh, and logs +- bottom status bar item that reflects project readiness and opens the dashboard - `Unity MCP: Check Status` -- `Unity MCP: Show Output` - -Slice 2 adds: - - `Unity MCP: Configure Project` -- shared `unity-mcp-cli` adapter usage for VS Code MCP config generation -- trusted-workspace guard for write actions -- transport picker for `HTTP` or `STDIO` - -Slice 3 adds: - - `Unity MCP: Install Plugin` -- shared `unity-mcp-cli` adapter usage for Unity package installation -- explicit confirmation before mutating `Packages/manifest.json` - -Slice 4 adds: - - `Unity MCP: Open Unity` -- plain launch or MCP-connected launch -- reuse of shared `openProject()` behavior from `unity-mcp-cli` +- `Unity MCP: Show Output` -Slice 5 adds: +## Safety Rules -- actionable status recommendations -- guided next-step buttons from `Unity MCP: Check Status` -- clearer first-run setup hints in the output report +- Untrusted workspaces stay read-only. +- File mutations always require an explicit command or button click. +- Unity launch is explicit and never automatic. +- Logs are structured and do not include auth tokens or generated config bodies. ## Local Development @@ -46,52 +30,82 @@ Slice 5 adds: cd vscode-extension npm install npm run build +npm test ``` -Then open the `vscode-extension/` folder in VS Code and press `F5`. +Then open `/Users/suporte/Unity-MCP/vscode-extension` in VS Code and press `F5`. + +## Package A VSIX Locally + +```bash +cd vscode-extension +npm install +npm run package:vsix +``` + +This produces a local `.vsix` package in the extension folder. + +To install it in VS Code: + +1. Open the Extensions view. +2. Click the `...` menu in the top-right. +3. Choose `Install from VSIX...`. +4. Select the generated `unity-mcp-vscode-0.0.1.vsix`. + +## Release Handoff + +This repo is prepared for local VSIX packaging, but not for publishing from a personal account. -## Manual Validation +- Maintainer handoff steps: [PUBLISHING.md](/Users/suporte/Unity-MCP/vscode-extension/PUBLISHING.md) +- Support guidance: [SUPPORT.md](/Users/suporte/Unity-MCP/vscode-extension/SUPPORT.md) -In the Extension Development Host: +## Manual Validation Matrix -1. Open a non-Unity folder. -2. Run `Unity MCP: Check Status`. -3. Confirm the output channel reports `Unity project detected: no`. -4. Open a Unity project folder. -5. Run `Unity MCP: Check Status`. -6. Confirm the output channel reports: - - trust state - - Unity markers - - plugin installed or missing - - `.vscode/mcp.json` present or missing +### Dashboard and Status Bar -Then: +1. Open a Unity project in VS Code. +2. Confirm the `Unity MCP` icon appears in the Activity Bar. +3. Confirm the bottom status bar item appears and changes by project state: + - `Install` + - `Init` + - `Configure` + - `Ready` +4. Click the status bar item and confirm it opens the dashboard. +5. Confirm the dashboard shows: + - `Next Step` + - `Workspace Status` + - `Actions` + - `Diagnostics` -7. Run `Unity MCP: Configure Project`. -8. Choose `HTTP` unless you specifically want to test `STDIO`. -9. Confirm `.vscode/mcp.json` is created. -10. Run `Unity MCP: Check Status` again and confirm the MCP server now appears as configured. +### Setup Flows -Then: +1. In a project without the package, run `Install Plugin`. +2. Confirm `Packages/manifest.json` is updated. +3. Open Unity and allow package import to complete. +4. Run `Configure Project`. +5. Confirm `.vscode/mcp.json` is created or updated. +6. Run `Check Status`. +7. Confirm the report shows: + - plugin installed + - Unity MCP project config present + - VS Code MCP configured -11. Run `Unity MCP: Install Plugin`. -12. Confirm the warning about updating `Packages/manifest.json`. -13. Approve the install. -14. Confirm the manifest is updated and the status report now shows the plugin as installed. +### Launch Flows -Then: +1. Run `Open Unity`. +2. Validate plain launch. +3. Run `Open Unity With MCP` from the dashboard or command flow. +4. If the project has not initialized yet, confirm the extension offers `Open Without MCP`. +5. After initialization, confirm connected launch succeeds. -15. Run `Unity MCP: Open Unity`. -16. First test `Open Unity`. -17. Then test `Open Unity With MCP Connection`. -18. If the plugin was just installed and the project has not initialized yet, the extension should offer a guided fallback to `Open Without MCP`. -19. After Unity imports and creates `UserSettings/AI-Game-Developer-Config.json`, retry `Open Unity With MCP Connection`. -20. Confirm the output channel shows the open-project progress events. +### Clean VSIX Install -Then: +1. Install the VSIX in a normal VS Code window, not the Extension Development Host. +2. Open a Unity project. +3. Use only the Activity Bar dashboard and status bar entry. +4. Confirm the full setup workflow still works without launching the extension from source. -21. Run `Unity MCP: Check Status` in projects representing different setup states. -22. Confirm the status report includes `Recommended next actions`. -23. Confirm the status notification offers relevant next-step buttons such as `Install Plugin`, `Configure Project`, `Open Unity`, or `Show Output`. +## Notes -Unity itself is not required for this slice. +- `HTTP` is the recommended transport when configuring VS Code MCP. +- The current package metadata uses a preview publisher id for local packaging. Marketplace publisher identity can change later without affecting the extension architecture. diff --git a/vscode-extension/SUPPORT.md b/vscode-extension/SUPPORT.md new file mode 100644 index 000000000..7087c43b5 --- /dev/null +++ b/vscode-extension/SUPPORT.md @@ -0,0 +1,22 @@ +# Support + +For bugs, setup problems, or packaging issues, open an issue in the main repository: + +- [Unity-MCP Issues](https://github.com/IvanMurzak/Unity-MCP/issues) + +When reporting a VS Code extension problem, include: + +- VS Code version +- extension version +- OS version +- whether the workspace was trusted +- whether the project already had the Unity MCP package installed +- the relevant `Unity MCP` output channel logs + +If the problem involves Unity launching, also include: + +- Unity editor version +- whether the issue happened on plain launch or MCP-connected launch +- whether it was the first Unity open after installing the package + +If Unity closes unexpectedly during setup, reopen the project and retry once. If the issue repeats, capture Unity `Editor.log` and file a bug with the exact step that triggered it. diff --git a/vscode-extension/media/marketplace-icon.png b/vscode-extension/media/marketplace-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..975a8b09beeadf34a2f7fd0e8c2df4207c4f8ce4 GIT binary patch literal 12553 zcmeHu`8(9>|Nm>oo{CZ=+47DKqNvC+Gb%?S&S^o1P+5-1Hd$ww(L!YzC0k@#PN}R> zhGZ$Sgqcptl0D4W2g8hEme2i~uFvQD{R=+V^|`KoaGB}8U(fr#pZ9ZlJRUFI`oqCy z!@BM3APCxEd+g{*2ttC7NJv@|{920qx(a?FuqSN}L&ek`li-V~w~MXMi4%}6_$&<} z68s=>cnbL04t~I+50U@5<;en zC>wPga<{2Pjond+9rD;L+O~>}G-Pt}Z2b_Q-51nQ&8#)YFML$$Gt21+;E&!Q#C?)XYfyFazK>Pq1o3h-e47jbdo#;vU% zQE6+7FVJmkbh8vy^Mh2mmInwa*s%c>L})LTZ+PzQ{&zybS_AMRnq@8&_IW$hx2EYd za}kt*aMw)i{4;pL&mI^k>xCqmztp*MOZeksqH)b=S5Vp+WG9d48d&8$-BfD_-|aY9 zb!+B+J1$DFGCIQJvKn-oxe?!!uSvpX&WepqK_N5zLKtzgE3h{4y%L9qKUstAOq#2X$xC#WezFwI=J4L@(i5<9q{gdxkw@74ly%J21-MQmpb z(Mk6L!@JK#Bm?iMqukz-&1}HFUIO0@oo0hOt23jL6F8;<{ot{dJ0U`%{kAKjG+70S z<@$B1prm$t5XV==qqz&dDt*nLql*&aM3t|*<))%Q%*F@R@!66?%g!bWEp)4VhX>o$ zq9-0{%BhBmEQNx({+C#7hIL7M95W~Rm4)xA8 z77guTW6Z2`XqygQhy}?RzD(!zV`=j%qa;J0stXzGeI_4r5!Jc{>Tr1HtqYh*F&K*B z446_0+{JNAiOVssrcHWU8yjj&jr4>wz2&1=nX(`&+J?l%(~aUq7GAs4Bs%1J64&82 zfVF2FJV%ij)Ffk5jR(l^xiR)|KBMP?N!wDHQynMcrq8O^H>appzs>$L@8ajtV^EuX^P?jPzh47^ z&o5bBR`0o<_y2E|oUgO!@@YCmPn8dfj!;!+!Rk z^vrPgasQpFJV#2B!v(sLc})Sx~(jJLXkH@!9>8}nMfYRjfcbkB3NlH5B zyMAq~HLYPYaNeamk8bCaW20TJ`e$5ne;4`c`n1FJ2m`EKsvGxa0~B@GL%VdD_mA1u zT@;#e__@inOBuUs=Re2}Fbln6G4a}=3BIb#Li;+cT2S*n{gD1jP_bLpmzct6-3v|6 zv3>8RT*Bh_mL)Uau*9M*^)&5Fg)FkrZQ$77lUTiHLz_H69VtB>_eKU%W-u*XV{|W| zCWF2NEU)+br!HIlGcL?J<4W!7RKcjj)kFEaii@*i9r3$B%-)sg+6m!V9VVl*g1qT( zr$GJoMX#MWSCTi(`g+PSd5y~%^w7kL8S%>h;cw005w0-cA~Y?bs0uAYIUIAQi%Fd2 ze7)G{w%4lD@gXgvTn3q0+i+oSop&o^KU(9!U-{`uoy?39P+%uCOIx7S5J7-_npDq{ zJB|E`9zdSXW)lR%>kI!F?_Qt`9o0KCu{DzP``Ri8tNuDo1QD`Nq~K`_qGfW8~vL ziW4oev?*&i6a}SG^)M2Vf5mj^42?|C>Q8| z=GP^0zh=l1v{#o=Wn+8%J6K;)%~bI+iA8V^Wi>%0t%x+E{rfuPj$Q7MoA^@(Bvt)M ziML>*+scZ~&8f>MBcI$?qo9Z{q0)_*f7sil_*-! zY&~M^aH_0ubKcj?<{$LOsj}%8nG(;u)3P6s8T-dNBTKZbG!!JTYQ0mHQ}h!x_!>h5 za$0Bav?|e!CtNNeY^Vv8bsTTan%_@lR?PoX_O>KjeZEn_G3nx>^4w%pw~n6V;=@Pn z&MmrwK`42RfatY(*_ZEA7$*Lyvw(>2k9nq>VNC}nN zjaA$MZldRCTrU!%?X}$B%51r#HCk57vl;r=S>uR7f}DEo+c6epD0VvZa7mqDb)H5L zl(*q1&tOX`cN%&NW?PYYmRSwL`Qy?skwT1)ERQ#Cdbd+`Zum*Mk+N#C{C#&S@2q|K)RjS z0^G6mcEzWg5@4R2J=i;q#)|J~+tgz8PW;W+{O(zSz75<(Jr&^N)ts*b=xOAevxn*& z__UBhw7nXmcQ-L+j_P&S<1mKy#MyMV*Oz$$Se6br3S}LSS@`j>fF-u54^ap@+1y(C zE1EKc!V5%UnMc7S6`6q3kTeDr6APf7Gg*PG^NqVD@`*=s4Zk`-av5$)(>xA4y=FVy zLT8SGHdcTA9_UgQ0Zrt~TP*f|dNDl8a}C>8qfv7a3}2=W zT=tX!@;DGiXObHoF-hO0J26i36Ks}Eg&c0+fsRSj-<%9TFnBE~&0F&mjyzY`i1j4En z1+cR&D=_i*bR(1DA)$ctjf=pd`aD(gyPw*!Pq)m*%DGQ{V4i?C=MUFp7&N51c3A3U zcpn@7&PC)@CeK6+#hAFSu*N>-+0cFZ!M7td(gVC{@sh&Xx2Lz=Z3E(A!~qz%OzV~&L}=7=8)JSn-qLjBtW7*2H6;cc zuJqrE1jRi8$E=CS-5m))BF8K}y*8+c3&ZA+Tr^=W%4_5A^b0#s_(+T7woF5gg{mkp z-YB{hKznU{*9%@XtEn`PvHUD9F4d&yN8u))X2wgY^44nq5DDI8P6 z)C2GAeHHrH^sSul0M6g7|4Wki0>eET$)4#AA#lax^5n8wH&?ptWu-nEZ6=W-^q#TY zC?#AkRoWoA6#I(gZO@r?o%Z+^wzf667FZx+ZV6xd*qc&r!0EHU* zor4W(($|upZN^3!QFc<+EZ&RIiab~7_wB^Q95oK^YfcpF0 z8#k(1^o;JW;OEHzWJo);8AoFI8dw|fvRy|+QSwdZo(!b$XW&-yAuN3g7+4sZZaG<% z=@A>%qLrWynAv$hme}UhML#ro)wr#WaVJEuEy@B*(~S%QCDrz!8I-9Gz(UH zrD06Pa>prQwCY*2OfEvlF>i%cCeMMjix5mohM^N7Iy zBj_Vrdq?&*y9d5bOTeo3$;*OgiFZt>H$&%pUHZo@@S^-yV09aJ46S z&cS{b#v4%FH(5|ivP{cg&!`Jmno~pO+vQrYGdR_3l~jUL7=HPi^AW*hlvggnmZcv3 z7wl4LeQQrG;1055li|Dh4;OqsI5N&m`e%64PYJ&`VNM%$DgCTEPsA|wp?YR^v@$=( zz%-EG%s2Dqzm0u~)yAqAQR|j0Q_xuU`78Z?O~S#JjJ| z?=&TR6imVi{t}Rt?lVS8rTKZ$m)vslxQLqWR*#72M1D7bNHl>((LO@B2pE=0h!bQGAaxrnp-N|mNrL5oiQ!fv~P$>7=ZVK4>N+aeDMBu&zK0MNvWm8A^wh{8DUw5KzD zWd52e@U2FvJ1AlYrw$AZQgsIUH@JN0T&xKBuIGQ(`(D+&W6t`bndEnSMb~MMf&PBj zak4^(j1SFKPmNhBC)I1KX77%g`ubwQ%C3oP@?vrII1ytF7^>8njxQCAJX~PEBic%X zBrJ-xSo(Zq^ds7F7AxBODkzE7&(G5(cN_}!>hA`$j~%KvbS}3mPcSjkkkH)6m)f|a zr?Y$c0=&u3&YECHviHet4Op4p3UWL2!`EH+JBd{RfvmSw~C@!UDtCw!{ zA1|MNlqg(B*z|5c@RTBubP6kX6M(jId+0pJFesv7KqXoNJXM%Ogi3$hE^3HB)H~b^ z`YQsNvkRU)IKB6g1LU$`7?-I7vJeR_cB|4Zl9KyssM)AcPq1r#5v=L*5Ptme*bj07 zL!T=TWw@z|F!47KF$E#~&H^>FD-({w+Si0uUFfH-kpXm#BQ@pf^(RSgyzcXPA5QXf zmB1dm&HxGHQE0Ct;f2C%ikhX_H6le+gq{L%#`yGq>=3Ndhfh@x86rIW&V-=A;#oG} ziRLK$h2bwwVirJ z_K_>G22s+fqE>V86tMPcF8i`@EX|R+l425DP8BMz!(tMrqk`C{jzJUq)HM_Y)I7`Ttn)XgU3hwMu(pFcXulaV z>uSmFx!ABU+FZgfNEaL;^T9$5u|arolmMCyUIbHsq=HL{TB3-mU1_ENxRBcuM*@z^FF z=U!LhCNOUnT%W|f`Qi818enGWRvCPL1&BHcdd|f&tA%9^9vQ29__o)p=bkHxe6A{! zC043cQ6qeWKTP}bu34Rc?>3ADEt5pyo#REEn3RA6(g_2(xcG5?IK{A6BH)OJ;)=xG zeyjMhU7}PS1Ry6Evj!)8eB;|5 z(ZZ$aAS}~Um_MyQMj|o)ewb1eE!=Y)tOZPXyxO-{->;MGFV)0>aMIQJl#7 z!{Fg;WhiMM?yt>$+MY1rO8LA`n zp$kSdUJc zCDszKhVb9=rpN09*xPYY5?w~n^$A!uMghrmeC5vj#ar#uyu`?AAg{iSQ>W)!fJjv&`0uxP) zXB@IzoLD{g*1w{dz#Y#V_~+A}F<2wmVS~cb+k*kUqH@`T(?Q}!RI)1{ z<`LVz3!ZWF2R!GIBHTp^>;|I|YKJ1X5P)jfdoI+D3#$cyh}VWZ22x&m z=CET>mihjw3C0hyJW#KuHesRYNt_2#JX*G05n5FL4$C~b&%6C)yGv?UFST5z!$21F z&DorywVLU7SsW6fiHq$6xNs}AK1?}%Y*~?Q=&E91EKH-IHoEX+oE4cZ2Bd?h~Z$oX@5rT_-C zm;y{UwD&TCTlO-@i1d;T|G8)G7JJBg0J|4HpE#6_HG9%N zaLmOx8@qWS9TY}aS$zY@K0n8mB7!k!4N7{sBfdn7^4cSM@~NhsBm*EMQf(8J8wTqLgpyNLBWv0k-cAJ6GUG*zC8_gVw?+OFzT(ue6v-s7VebS7j z$%R$)N%~Q*ET2iN)-%>J9L`%M4(^2PZ?x^9cN+M|e#Qqd=SG6OfQptk_J?)=k zR-q5C!ihBQsNNk#xL))8RyT}dPKpO|nL3Rq+@!%#jRB7@DG_Idd5rZl^p=Xo>@!2y zgvLo;!ECDOavMx93#(yH_JU~lofT`AA11h8wE@ucx3MAst?!6|DvPyyHy9R3WXyl9 zo_4srM{uA)uWCN%)2Yqfk9RxV1=}@|mxlt0ckBN8gk=VWMklUK+n2VR3IXf{+`8Dz z;U<_v06B&tAyx@5K3Wu{Ye zi2?m-Md9U{m^Y)a*vf7NgzS0O0Y1R{^}hr5hC3~oDJRR9_MPLgC$5C^jjD!ybA|IG z4b`!G3`HohO+g-FS8Df0px2nb>Vhx}kWss0&VKjJC8+96E@diN6tz9N^+waq*+^RyoZ!lk1XHr9J`{!tjsGlS!9ws>t zunT0ml>X#MqA~*wdUNWAn4ej97MwO|nHr3Vmxx9`GqK}>UDzydXOI_^>Rn-^Uwv2>#0#=7YIPg3b7=p4gOT`I>61DeM22Vz%SVoB+IS`f3s1e;Z~RYMtKGC z>{YnGD*$MVeFR9hoU$1~z$?AYMM%l1?a*0KHM{a0>sVt6Vp>|?7C`u82g)7<$4kJk zL@W5$>bbDOjP8 z0MOO};!V+o0o15G(;g%J)N#C{#X@5#K`4-5fBFr^;q`aL#fiJljK0OT@`#kdX#bXT zaYZTkFEsc)tH>`HPjldy@Ox&I*3kDB;VYmWj4DWBork+zgH+LzKxokaN!6h^MQ}K4 zJ6CW5PzjC>ckSeVhTj?jR`3vrk$=~Jxd*4L^yy9V(96>&(eMBfbBzdnCO-WQ9*}>& zQv*8v;-6S}0E&|Y<`8zUG7rpIW1#0JZTav30=WU)gjj~?9XsaXZ9oa0-dz<1f%tZC z97%mx*94}w0t3f)EsJ`s^nO5oH@MvY`3DQSz(D%(DN$I+{|&sfy>ve;{s3tX7|>1s zDkoa?vxWw=-_!57h;6`n%NgAl>c0;3>xO=X92EB}hd|BxRTO@e?O%Q9S7ZH!6n=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.11.0.tgz", + "integrity": "sha512-zkGNYS3TwY8lUpPIafAmsFCYZbgFixY9y/LZB9GUg0IILoHTqpN26j5OrkL1AQThh/YdZsawe4iWXfp85lFVxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.6.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.6.2.tgz", + "integrity": "sha512-hQjjsekAjB00cM1EmatWJlzhEoK2Qhz7Rj5gvM6tYf8iL7RM3tkxlpU9fG0+ofkulzg9AEEA6dIEnSmDr5ZqUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.2.2.tgz", + "integrity": "sha512-toS+2AePxqyzb0YOKttDOOiSl3jrkK9aiqIvpurpis0O34QcIS5gToqrgT39p04Dpxw3YoUU0lxJKTpSFFfA6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.6.2", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -470,6 +688,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.61.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", @@ -820,564 +1076,3805 @@ "win32" ] }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", - "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/vscode": { - "version": "1.120.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.120.0.tgz", - "integrity": "sha512-feaT4Rst+FkTch5zz/ZbNCxoIvo55YU80Be2kiL7OJcod4+CUYf2lUBPdIJzozNnSEMq1VRTGrWEcCGFB3fBmA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitest/expect": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", - "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", "dev": true, "license": "MIT", "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.6", - "@vitest/utils": "3.2.6", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@vitest/mocker": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", - "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.6", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", - "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@vitest/runner": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", - "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.6", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "node-sarif-builder": "^3.2.0" } }, - "node_modules/@vitest/snapshot": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", - "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.6", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" + "@secretlint/types": "^10.2.2" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@vitest/spy": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", - "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@vitest/utils": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", - "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.6", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.7.1.tgz", + "integrity": "sha512-Wii5UgUKFEh9Uv6wbq1zr4/Kf+dtjiUuzPrrXzKp8H+ifkvKNzi23V4Nz+6wVyHQn5T28AFuc8VH8OtzvGYecA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.7.1.tgz", + "integrity": "sha512-TdwZ/debWYFD05K3CcoHtwvnCrza29wZxD+BjDTk/V5N7iRqkK1dTTHSD4A8AIgROLiDkHJmIKQbasbmsg8AvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.7.1", + "@textlint/resolver": "15.7.1", + "@textlint/types": "15.7.1", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.7.1.tgz", + "integrity": "sha512-Jg+sQW2L/cRJypk59wtcMUVVpt8vmit5ZMT3gUnFwevP3A6Qp1HfOtUy9ObT4hBX3lOSGT/ekcCDxR1pL7uH1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.7.1.tgz", + "integrity": "sha512-8XnO0pgF6mXnm41VvWmBbEIdGPhiCUt31uLZkOis1ECeg/1SoUcIT6Mx/F0e1rukq8l0UlOSeY9a31CsvRMK0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.7.1.tgz", + "integrity": "sha512-Vye/GmFNBTgVzZFtIFJTmLB+s2A7oIADxNG6r9UhfPuY+Czv0z5G3xeyFZZudPlfxURsKUyPIU5XsjOFqVp33A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.7.1" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vscode": { + "version": "1.120.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.120.0.tgz", + "integrity": "sha512-feaT4Rst+FkTch5zz/ZbNCxoIvo55YU80Be2kiL7OJcod4+CUYf2lUBPdIJzozNnSEMq1VRTGrWEcCGFB3fBmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.5.tgz", + "integrity": "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.6", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vscode/vsce": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", + "integrity": "sha512-XSxMosEEDO6vLxELAHVkwmhC0qe0ijZni2jB9Rcs8kQsW4lhTDQ/wMzmwFs/buotAWSnpmUp/dRWD2ufG3UYKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^13.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^10.2.2", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^3.2.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@vscode/vsce/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/linkify-it": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", + "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "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", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", + "optional": true, "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, "engines": { - "node": ">= 16" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "license": "MIT", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=18" + "node": ">=0.10.0" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } + "license": "MIT" }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/estree": "^1.0.0" + "safe-buffer": "~5.2.0" } }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=12.0.0" + "node": ">=8" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "node": ">=8" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=8" } }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "js-tokens": "^9.0.1" }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", "dev": true, - "license": "MIT" + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 14.16" + "node": ">=8" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "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==", + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, "engines": { - "node": ">=12" + "node": ">=14.18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=10.0.0" } }, - "node_modules/rollup": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", - "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.61.1", - "@rollup/rollup-android-arm64": "4.61.1", - "@rollup/rollup-darwin-arm64": "4.61.1", - "@rollup/rollup-darwin-x64": "4.61.1", - "@rollup/rollup-freebsd-arm64": "4.61.1", - "@rollup/rollup-freebsd-x64": "4.61.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", - "@rollup/rollup-linux-arm-musleabihf": "4.61.1", - "@rollup/rollup-linux-arm64-gnu": "4.61.1", - "@rollup/rollup-linux-arm64-musl": "4.61.1", - "@rollup/rollup-linux-loong64-gnu": "4.61.1", - "@rollup/rollup-linux-loong64-musl": "4.61.1", - "@rollup/rollup-linux-ppc64-gnu": "4.61.1", - "@rollup/rollup-linux-ppc64-musl": "4.61.1", - "@rollup/rollup-linux-riscv64-gnu": "4.61.1", - "@rollup/rollup-linux-riscv64-musl": "4.61.1", - "@rollup/rollup-linux-s390x-gnu": "4.61.1", - "@rollup/rollup-linux-x64-gnu": "4.61.1", - "@rollup/rollup-linux-x64-musl": "4.61.1", - "@rollup/rollup-openbsd-x64": "4.61.1", - "@rollup/rollup-openharmony-arm64": "4.61.1", - "@rollup/rollup-win32-arm64-msvc": "4.61.1", - "@rollup/rollup-win32-ia32-msvc": "4.61.1", - "@rollup/rollup-win32-x64-gnu": "4.61.1", - "@rollup/rollup-win32-x64-msvc": "4.61.1", - "fsevents": "~2.3.2" + "node": ">=8" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true, "license": "MIT" }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", "dev": true, - "license": "MIT", + "license": "Artistic-2.0", "dependencies": { - "js-tokens": "^9.0.1" + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://bevry.me/fund" } }, "node_modules/tinybench": { @@ -1441,6 +4938,85 @@ "node": ">=14.0.0" } }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -1455,6 +5031,30 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.27.1.tgz", + "integrity": "sha512-UDdpiex+mzigiyrXrGbiUaF4HzTNhKbh2vRNFaTMzcqmLIPrZxaCtwo/1TMSuWoM1Xz3WiTo9KdgI3kRqYzJGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -1462,6 +5062,19 @@ "dev": true, "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unity-mcp-cli": { "version": "0.79.0", "resolved": "https://registry.npmjs.org/unity-mcp-cli/-/unity-mcp-cli-0.79.0.tgz", @@ -1479,6 +5092,55 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/vite": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", @@ -1650,6 +5312,30 @@ } } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -1667,6 +5353,84 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.2.tgz", + "integrity": "sha512-Md9ankxxN23wncAN8s7+Tn3Co52zLUPMtnrLAbVCnfG5d2tKBFfmygYSgXlqFgXObtzIgqkx7aNgDBpso9+4qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, "node_modules/yocto-spinner": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.0.tgz", diff --git a/vscode-extension/package.json b/vscode-extension/package.json index dce20f9a6..908076244 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -1,9 +1,24 @@ { "name": "unity-mcp-vscode", "displayName": "Unity MCP - VS Code Integration", - "description": "Preview VS Code integration for Unity MCP local project setup and diagnostics.", + "description": "Preview VS Code extension for Unity MCP project setup, diagnostics, plugin install, and Unity launch flows.", "version": "0.0.1", + "publisher": "unity-mcp-preview", + "preview": true, "license": "Apache-2.0", + "icon": "media/marketplace-icon.png", + "galleryBanner": { + "color": "#111111" + }, + "homepage": "https://github.com/IvanMurzak/Unity-MCP/tree/main/vscode-extension", + "bugs": { + "url": "https://github.com/IvanMurzak/Unity-MCP/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/IvanMurzak/Unity-MCP.git", + "directory": "vscode-extension" + }, "engines": { "vscode": "^1.99.0" }, @@ -122,12 +137,14 @@ "watch": "tsc -watch -p ./", "pretest": "npm run build", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "package:vsix": "vsce package" }, "dependencies": { "unity-mcp-cli": "^0.79.0" }, "devDependencies": { + "@vscode/vsce": "^3.6.1", "@types/node": "^22.15.0", "@types/vscode": "^1.99.0", "typescript": "^5.8.0", From e760c46e14680cad187748860c68167bed06944b Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 13:33:47 -0500 Subject: [PATCH 09/12] docs: finalize vscode extension guides --- docs/dev/issue-613-vscode-extension-plan.md | 212 -------------------- vscode-extension/.vscodeignore | 1 - vscode-extension/DEVELOPMENT.md | 144 +++++++++++++ vscode-extension/PUBLISHING.md | 23 ++- vscode-extension/README.md | 203 +++++++++++-------- vscode-extension/SUPPORT.md | 137 ++++++++++++- 6 files changed, 406 insertions(+), 314 deletions(-) delete mode 100644 docs/dev/issue-613-vscode-extension-plan.md create mode 100644 vscode-extension/DEVELOPMENT.md diff --git a/docs/dev/issue-613-vscode-extension-plan.md b/docs/dev/issue-613-vscode-extension-plan.md deleted file mode 100644 index 8a7bbaf70..000000000 --- a/docs/dev/issue-613-vscode-extension-plan.md +++ /dev/null @@ -1,212 +0,0 @@ -# Issue 613 Plan: VS Code Extension Marketplace - -## Goal - -Create a VS Code extension package for Unity MCP that is safe to install from the VS Code Extensions Marketplace, reuses existing repo logic where practical, and reduces the current manual `.vscode/mcp.json` setup burden without widening the Unity MCP trust surface unnecessarily. - -## Current Findings - -- Issue [`#613`](https://github.com/IvanMurzak/Unity-MCP/issues/613) is minimal and open. Its only explicit acceptance signal today is: make a VS Code extension users can install from the marketplace. -- The repo is Apache-2.0 licensed. No `CONTRIBUTING*`, CLA, or DCO files were found in the repository root. -- The CLI already exposes a side-effect-free library API in [cli/src/lib.ts](/Users/suporte/Unity-MCP/cli/src/lib.ts) and documents it in [cli/README.md](/Users/suporte/Unity-MCP/cli/README.md). -- `setupMcp()` already knows how to generate VS Code MCP config for agent id `vscode-copilot` and targets `.vscode/mcp.json` via [cli/src/utils/agents.ts](/Users/suporte/Unity-MCP/cli/src/utils/agents.ts) and [cli/src/lib/setup-mcp.ts](/Users/suporte/Unity-MCP/cli/src/lib/setup-mcp.ts). -- The Unity-side VS Code configurator in [VisualStudioCodeCopilotConfigurator.cs](/Users/suporte/Unity-MCP/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Editor/Scripts/UI/AiAgentConfigurators/Impl/VisualStudioCodeCopilotConfigurator.cs) still tells users to create `.vscode/mcp.json` manually and start the `ai-game-developer` server manually after each VS Code restart. -- Current VS Code docs show: - - MCP servers are managed through `mcp.json`, the Extensions view, and inline code lenses. - - VS Code has a current MCP developer guide and workspace trust guidance. - - Workspace Trust should be treated explicitly for extensions that touch local projects or execute tools. - -## Working Decisions - -- Use a new isolated package folder: `vscode-extension/`. -- Reuse `unity-mcp-cli` library functions where possible. Avoid shelling out by default. -- Keep v1 local-first. No telemetry. No publishing flow changes in this repo yet. -- Treat write actions and Unity launch actions as trust-sensitive and opt-in only. -- Start with read-only diagnostics before adding project mutation commands. - -## Slice Plan - -### Slice 1: Extension scaffold and read-only status - -Scope: -- Add a standalone VS Code extension package. -- Add safe activation, output logging, Workspace Trust awareness, Unity project detection, plugin detection, and MCP config detection. -- Add `Unity MCP: Check Status` and `Unity MCP: Show Output`. - -Why first: -- Gives a testable extension immediately. -- Builds the logging and trust baseline needed for later write flows. -- Avoids any project mutation while we confirm extension packaging and UX. - -Automated tests: -- Unit test Unity project detection. -- Unit test plugin detection from `Packages/manifest.json`. -- Unit test `.vscode/mcp.json` parsing and warning paths. - -Debug logging to include: -- `activate:start` -- `activate:complete` -- `workspace:pick` -- `status:computeStart` -- `status:computeResult` -- `status:error` -- `trust:granted` - -Manual validation: -1. Install extension package dependencies. -2. Build the extension. -3. Launch the Extension Development Host from VS Code. -4. Open a non-Unity folder and run `Unity MCP: Check Status`. -5. Open a Unity project and run `Unity MCP: Check Status`. -6. Confirm the `Unity MCP` output channel reports trust state, Unity detection, plugin status, and MCP config status. - -### Slice 2: Shared CLI adapter for setup flows - -Scope: -- Add a small internal adapter that imports typed functions from `unity-mcp-cli`. -- Prove runtime compatibility inside the VS Code extension host. -- Avoid any duplicated MCP config generation logic. - -Automated tests: -- Adapter success/failure mapping. -- Progress event forwarding. -- Config generation parity check for `vscode-copilot`. - -Debug logging to include: -- `cliAdapter:loadStart` -- `cliAdapter:loadSuccess` -- `cliAdapter:loadFailure` -- `cliAdapter:callStart` -- `cliAdapter:callSuccess` -- `cliAdapter:callFailure` - -Manual validation: -1. Run a dry adapter command from the extension without mutating files. -2. Confirm output channel logs progress events. - -### Slice 3: Configure Project command - -Scope: -- Add `Unity MCP: Configure Project`. -- Generate or update `.vscode/mcp.json` through shared library logic. -- Require trusted workspace and explicit user action. - -Automated tests: -- Creates `.vscode/mcp.json` when missing. -- Preserves unrelated config content where appropriate. -- Re-running is idempotent. -- Errors are surfaced clearly when the workspace is not a Unity project. - -Debug logging to include: -- `configure:start` -- `configure:precheck` -- `configure:writeSuccess` -- `configure:writeFailure` - -Manual validation: -1. Trust the workspace. -2. Run `Unity MCP: Configure Project`. -3. Inspect `.vscode/mcp.json`. -4. Open VS Code MCP server UI and confirm the server entry appears. - -### Slice 4: Install Plugin command - -Scope: -- Add `Unity MCP: Install Plugin` if `installPlugin()` reuse is clean and safe. -- Keep user confirmation explicit. -- Never silently install anything. - -Automated tests: -- Plugin install happy path. -- Already-installed path. -- Failure path for missing or invalid Unity project. - -Debug logging to include: -- `pluginInstall:start` -- `pluginInstall:precheck` -- `pluginInstall:result` -- `pluginInstall:error` - -Manual validation: -1. Use a Unity project without the package installed. -2. Run `Unity MCP: Install Plugin`. -3. Confirm `Packages/manifest.json` is updated as expected. -4. Open Unity and confirm package import begins. - -### Slice 5: Optional convenience actions - -Scope: -- Evaluate `Unity MCP: Open Unity`. -- Evaluate minimal safe guidance for starting or managing the MCP server. -- Only include commands that do not hide risky behavior. - -Automated tests: -- Command registration and trust gating. -- Error handling for missing Unity/editor discovery issues. - -Debug logging to include: -- `openUnity:start` -- `openUnity:result` -- `openUnity:error` - -Manual validation: -1. Run the command from a trusted Unity workspace. -2. Confirm it only performs the documented action. - -### Slice 6: Packaging, docs, and hardening - -Scope: -- Add extension README, local install docs, release notes stub, and manual test matrix. -- Add extension-host smoke tests if needed. -- Prepare for future marketplace packaging without publishing. - -Automated tests: -- Build. -- Unit tests. -- Extension smoke test. -- Package smoke test. - -Debug logging to review: -- Ensure all logs stay structured and redact secrets. - -Manual validation: -1. Package the extension locally. -2. Install the VSIX in a clean VS Code profile. -3. Re-run slice 1 to 5 validation steps. - -## Security, Privacy, and Workspace Trust Rules - -- No telemetry in v1. -- Never log auth tokens, headers, prompts, file contents, or generated config bodies. -- Default to read-only behavior in untrusted workspaces. -- Require trusted workspace for any file mutation or Unity launch action. -- Do not auto-start destructive or code-executing flows. -- Keep all networked or editor-mutating behavior explicit and user-initiated. - -## Slice 1 Validation Steps - -### Validate in VS Code - -1. Open the `vscode-extension/` folder in VS Code. -2. Run `npm install`. -3. Run `npm run build`. -4. Press `F5` to start the Extension Development Host. -5. In the Extension Development Host: - - Open any non-Unity folder and run `Unity MCP: Check Status`. - - Open a Unity project folder and run `Unity MCP: Check Status`. - - Run `Unity MCP: Show Output`. -6. Confirm the output channel shows: - - workspace trust state - - selected workspace folder - - Unity markers detected - - plugin installed or missing - - `.vscode/mcp.json` present or missing - -### Validate with Unity - -Unity is not required for Slice 1. - -If you want a realistic signal: -1. Open a Unity project that already has `Assets/`, `ProjectSettings/`, and `Packages/manifest.json`. -2. If the Unity MCP package is installed, confirm the status command reports it. -3. If the package is not installed, confirm the status command reports it as missing. diff --git a/vscode-extension/.vscodeignore b/vscode-extension/.vscodeignore index a71abd56a..5a0e95fd5 100644 --- a/vscode-extension/.vscodeignore +++ b/vscode-extension/.vscodeignore @@ -7,4 +7,3 @@ vitest.config.* **/*.test.* .DS_Store *.vsix -PUBLISHING.md diff --git a/vscode-extension/DEVELOPMENT.md b/vscode-extension/DEVELOPMENT.md new file mode 100644 index 000000000..e11b47432 --- /dev/null +++ b/vscode-extension/DEVELOPMENT.md @@ -0,0 +1,144 @@ +# Development Guide + +This guide is for maintainers changing the VS Code extension itself. + +## Local Workflow + +Run from `/Users/suporte/Unity-MCP/vscode-extension`: + +```bash +npm install +npm run build +npm test +``` + +To debug the extension host: + +1. Open `/Users/suporte/Unity-MCP/vscode-extension` in VS Code. +2. Press `F5`. +3. In the Extension Development Host, open a Unity project. +4. Use the dashboard, status bar, or command palette. +5. Watch the `Unity MCP` output channel. + +To package a local VSIX: + +```bash +npm run package:vsix +``` + +## Source Map + +- `src/extension.ts` + Command registration, trust gates, notifications, status bar wiring, and dashboard refresh orchestration. +- `src/dashboard.ts` + Activity Bar dashboard webview, button layout, and status bar label computation. +- `src/projectStatus.ts` + Workspace inspection, recommended next actions, and the text status report. +- `src/cliAdapter.ts` + Shared-library bridge to `unity-mcp-cli`. Prefer extending this instead of duplicating CLI logic in the extension. +- `src/unityConfig.ts` + Reads and normalizes `UserSettings/AI-Game-Developer-Config.json`. +- `src/workspace.ts` + Workspace selection and preferred-folder behavior for multi-root workspaces. +- `src/logging.ts` + Output channel logger and log-level filtering. + +## Rules For Changes + +- keep write actions explicit +- keep untrusted workspaces read-only +- do not log tokens or generated config bodies +- prefer reusing `unity-mcp-cli` logic through `cliAdapter.ts` +- add or update tests when status logic, dashboard logic, or adapter behavior changes + +## How To Change Common Things + +### Add A New Command + +1. Add the command contribution in `package.json`. +2. Register the command in `src/extension.ts`. +3. If the command should appear in the dashboard, add it in `src/dashboard.ts`. +4. If the command changes setup state, call `refreshUi()` after the operation completes. +5. Add or update tests. + +### Change Setup-State Logic + +Update `src/projectStatus.ts`. + +That file controls: + +- Unity project detection +- plugin detection +- Unity MCP project config detection +- `.vscode/mcp.json` detection +- recommended next actions +- the text status report + +If you change state logic, also review: + +- `buildStatusBarPresentation()` in `src/dashboard.ts` +- dashboard primary actions in `src/dashboard.ts` +- status notification action routing in `src/extension.ts` + +### Change Dashboard UX + +Update `src/dashboard.ts`. + +This file controls: + +- `Next Step` +- workspace state cards +- button ordering +- button-to-command wiring +- Activity Bar view rendering + +If the dashboard changes, verify: + +- it renders after `dashboard:viewResolved` +- buttons still call the expected commands +- the status bar still reflects the right state + +### Change File Generation Behavior + +Prefer changing the shared library in `unity-mcp-cli` first, then keep `src/cliAdapter.ts` thin. + +Avoid copying setup logic into the extension unless the shared library cannot support the needed behavior cleanly. + +## Tests + +Current test coverage is centered on: + +- adapter behavior +- dashboard state mapping +- project status detection +- Unity config parsing + +Run: + +```bash +npm test +``` + +When changing packaging behavior, also run: + +```bash +npm run package:vsix +``` + +## Useful Files During Debugging + +Inspect these when debugging a real project: + +- `Packages/manifest.json` +- `.vscode/mcp.json` +- `UserSettings/AI-Game-Developer-Config.json` + +The extension should explain its view of these files through: + +- `Unity MCP: Check Status` +- the dashboard `Diagnostics` card +- the `Unity MCP` output channel + +## Release Handoff + +This extension is ready for local packaging, but Marketplace publishing should be done by the official maintainer account, not a personal account. See [PUBLISHING.md](./PUBLISHING.md). diff --git a/vscode-extension/PUBLISHING.md b/vscode-extension/PUBLISHING.md index fdc672bf6..1513d1341 100644 --- a/vscode-extension/PUBLISHING.md +++ b/vscode-extension/PUBLISHING.md @@ -1,16 +1,17 @@ # Publishing Handoff -This extension is prepared for local VSIX packaging, but it is intentionally not configured for publishing from a personal account. +This extension is ready for local VSIX packaging and maintainer handoff, but it is intentionally not configured to publish from a personal account. ## Before Publishing -1. Replace the temporary `publisher` value in [package.json](/Users/suporte/Unity-MCP/vscode-extension/package.json) with the official Marketplace publisher id. -2. Confirm the extension `name` and `displayName` are acceptable and unique for the Marketplace listing. -3. Review the Marketplace icon in `media/marketplace-icon.png`. -4. Review `README.md`, `CHANGELOG.md`, and `SUPPORT.md` for the final public wording. -5. Decide whether the first Marketplace release should remain marked as preview. +1. Replace the temporary `publisher` value in `package.json` with the official Marketplace publisher id. +2. Confirm `name`, `displayName`, and the Marketplace description are acceptable for the public listing. +3. Confirm whether the first public release should keep `preview: true`. +4. Review `README.md`, `CHANGELOG.md`, `SUPPORT.md`, and `DEVELOPMENT.md`. +5. Review `media/marketplace-icon.png`. +6. Verify the package still builds, tests, and packages cleanly. -## Verification +## Verification Checklist Run from `/Users/suporte/Unity-MCP/vscode-extension`: @@ -29,14 +30,16 @@ Then install the generated VSIX in a normal VS Code window and verify: - `Install Plugin` works - `Configure Project` works - `Open Unity` works +- `Open Unity With MCP` works after Unity has initialized the project config ## Publish -Use the official publisher account and the VS Code publishing tooling to publish from the extension folder. The official documentation is: +Use the official publisher account and the VS Code publishing tooling from the extension folder. Official documentation: - [Publishing Extensions](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) ## Notes -- The package metadata already includes `README.md`, `CHANGELOG.md`, `LICENSE.txt`, `SUPPORT.md`, and a PNG icon for Marketplace readiness. -- The current `publisher` value is only a placeholder for local packaging and should not be used for the real release. +- The package metadata already includes README, changelog, support guidance, license, and a Marketplace PNG icon. +- The current `publisher` value is only a handoff placeholder and should not be used for the real Marketplace release. +- If packaging changes, rerun `npm run package:vsix` before publishing. diff --git a/vscode-extension/README.md b/vscode-extension/README.md index 53b6af36d..0a1f12a9b 100644 --- a/vscode-extension/README.md +++ b/vscode-extension/README.md @@ -1,111 +1,148 @@ # Unity MCP VS Code Extension -This package is the isolated VS Code extension workspace for GitHub issue `#613`. +This folder contains the VS Code extension for Unity MCP. The extension is designed to reduce manual project setup inside VS Code while keeping file writes and Unity launch behavior explicit and easy to debug. -It is currently a preview extension focused on local project setup and diagnostics for Unity MCP. It does not publish telemetry and it only performs file mutations or Unity launch actions after explicit user action in a trusted workspace. +## What The Extension Does -## Current Capabilities +- shows a `Unity MCP` dashboard in the Activity Bar +- shows project readiness in the VS Code status bar +- detects whether the current workspace looks like a Unity project +- detects whether the Unity MCP package is installed +- detects whether the Unity MCP project config exists +- generates or updates `.vscode/mcp.json` +- installs the Unity MCP package into `Packages/manifest.json` +- launches Unity in plain mode or with MCP connection settings + +## Safety Model + +- untrusted workspaces are read-only +- file mutations only happen after an explicit command or button click +- Unity launch is explicit and never automatic +- logs are structured and do not include tokens or generated config bodies + +## Install A Local VSIX + +```bash +cd vscode-extension +npm install +npm run build +npm test +npm run package:vsix +``` + +Then install `unity-mcp-vscode-0.0.1.vsix` in VS Code: + +1. Open the Extensions view. +2. Open the `...` menu in the top-right. +3. Choose `Install from VSIX...`. +4. Select the generated `.vsix` file from this folder. + +## Normal Usage + +### Recommended First-Time Flow + +1. Open a Unity project in VS Code. +2. Trust the workspace if VS Code asks. +3. Open the `Unity MCP` view from the Activity Bar or click the bottom status bar item. +4. If the dashboard says `Install`, run `Install Plugin`. +5. Open Unity once and let the package import and compile. +6. If the dashboard says `Configure`, run `Configure Project`. +7. Run `Check Status` or use the dashboard refresh action. +8. When the dashboard says `Ready`, use `Open Unity With MCP`. + +### Dashboard States + +- `Install` + The Unity MCP package is not installed in `Packages/manifest.json`. +- `Init` + The package is installed, but `UserSettings/AI-Game-Developer-Config.json` does not exist yet. Open Unity once without MCP so the package can initialize. +- `Configure` + The Unity-side config exists, but `.vscode/mcp.json` is missing or incomplete. +- `Ready` + The project looks ready for connected Unity MCP use. + +### Commands -- `Unity MCP` Activity Bar dashboard with: - - current workspace setup state - - recommended next step - - action buttons for install, configure, launch, refresh, and logs -- bottom status bar item that reflects project readiness and opens the dashboard - `Unity MCP: Check Status` + Recomputes the current workspace status and prints a full report to the `Unity MCP` output channel. - `Unity MCP: Configure Project` + Writes or updates `.vscode/mcp.json` using the shared `unity-mcp-cli` setup logic. - `Unity MCP: Install Plugin` + Updates `Packages/manifest.json` to include `com.ivanmurzak.unity.mcp`. - `Unity MCP: Open Unity` + Lets you choose plain launch or connected launch. +- `Unity MCP: Show Dashboard` + Opens the Activity Bar dashboard. - `Unity MCP: Show Output` + Opens the `Unity MCP` output channel. -## Safety Rules +## Files The Extension Touches -- Untrusted workspaces stay read-only. -- File mutations always require an explicit command or button click. -- Unity launch is explicit and never automatic. -- Logs are structured and do not include auth tokens or generated config bodies. +Read-only checks: -## Local Development +- `Packages/manifest.json` +- `.vscode/mcp.json` +- `UserSettings/AI-Game-Developer-Config.json` +- workspace trust state +- Unity project markers such as `Assets/` and `ProjectSettings/` -```bash -cd vscode-extension -npm install -npm run build -npm test +Mutations: + +- `Install Plugin` updates `Packages/manifest.json` +- `Configure Project` writes `.vscode/mcp.json` +- `Open Unity` launches the Unity editor but does not rewrite project files by itself + +## Debugging And Troubleshooting + +Start here: + +1. Run `Unity MCP: Show Output`. +2. Set `unityMcp.logLevel` to `debug` if you need more detail. +3. Re-run the failing action. +4. Capture the `Unity MCP` output channel logs. + +Common logs: + +- `status:*` + workspace detection and diagnostics +- `configure:*` + VS Code MCP config generation +- `pluginInstall:*` + Unity package installation +- `openUnity:*` + Unity launch flow +- `dashboard:*` + Activity Bar dashboard lifecycle and button actions +- `cliAdapter:*` + calls into the shared `unity-mcp-cli` library + +If Unity behaves unexpectedly, also capture Unity editor logs. On macOS, the main editor log is usually: + +```text +~/Library/Logs/Unity/Editor.log ``` -Then open `/Users/suporte/Unity-MCP/vscode-extension` in VS Code and press `F5`. +More debugging guidance and issue-report checklists are in [SUPPORT.md](./SUPPORT.md). + +## Change The Extension -## Package A VSIX Locally +For local development: ```bash cd vscode-extension npm install -npm run package:vsix +npm run build +npm test ``` -This produces a local `.vsix` package in the extension folder. +Then open `/Users/suporte/Unity-MCP/vscode-extension` in VS Code and press `F5` to launch an Extension Development Host. -To install it in VS Code: +Detailed maintainer guidance is in: -1. Open the Extensions view. -2. Click the `...` menu in the top-right. -3. Choose `Install from VSIX...`. -4. Select the generated `unity-mcp-vscode-0.0.1.vsix`. +- [DEVELOPMENT.md](./DEVELOPMENT.md) +- [SUPPORT.md](./SUPPORT.md) +- [PUBLISHING.md](./PUBLISHING.md) ## Release Handoff -This repo is prepared for local VSIX packaging, but not for publishing from a personal account. - -- Maintainer handoff steps: [PUBLISHING.md](/Users/suporte/Unity-MCP/vscode-extension/PUBLISHING.md) -- Support guidance: [SUPPORT.md](/Users/suporte/Unity-MCP/vscode-extension/SUPPORT.md) - -## Manual Validation Matrix - -### Dashboard and Status Bar - -1. Open a Unity project in VS Code. -2. Confirm the `Unity MCP` icon appears in the Activity Bar. -3. Confirm the bottom status bar item appears and changes by project state: - - `Install` - - `Init` - - `Configure` - - `Ready` -4. Click the status bar item and confirm it opens the dashboard. -5. Confirm the dashboard shows: - - `Next Step` - - `Workspace Status` - - `Actions` - - `Diagnostics` - -### Setup Flows - -1. In a project without the package, run `Install Plugin`. -2. Confirm `Packages/manifest.json` is updated. -3. Open Unity and allow package import to complete. -4. Run `Configure Project`. -5. Confirm `.vscode/mcp.json` is created or updated. -6. Run `Check Status`. -7. Confirm the report shows: - - plugin installed - - Unity MCP project config present - - VS Code MCP configured - -### Launch Flows - -1. Run `Open Unity`. -2. Validate plain launch. -3. Run `Open Unity With MCP` from the dashboard or command flow. -4. If the project has not initialized yet, confirm the extension offers `Open Without MCP`. -5. After initialization, confirm connected launch succeeds. - -### Clean VSIX Install - -1. Install the VSIX in a normal VS Code window, not the Extension Development Host. -2. Open a Unity project. -3. Use only the Activity Bar dashboard and status bar entry. -4. Confirm the full setup workflow still works without launching the extension from source. - -## Notes - -- `HTTP` is the recommended transport when configuring VS Code MCP. -- The current package metadata uses a preview publisher id for local packaging. Marketplace publisher identity can change later without affecting the extension architecture. +This repo is prepared for local VSIX packaging, but not for publishing from a personal account. The current `publisher` value is a placeholder for packaging and handoff only. diff --git a/vscode-extension/SUPPORT.md b/vscode-extension/SUPPORT.md index 7087c43b5..dab80470c 100644 --- a/vscode-extension/SUPPORT.md +++ b/vscode-extension/SUPPORT.md @@ -1,22 +1,143 @@ -# Support +# Support And Debugging -For bugs, setup problems, or packaging issues, open an issue in the main repository: +Use this document when the extension behaves incorrectly, Unity behaves unexpectedly during setup, or you need to prepare a good bug report. -- [Unity-MCP Issues](https://github.com/IvanMurzak/Unity-MCP/issues) +## Collect The Right Information First -When reporting a VS Code extension problem, include: +Always capture: - VS Code version - extension version - OS version - whether the workspace was trusted -- whether the project already had the Unity MCP package installed +- whether the Unity MCP package was already installed - the relevant `Unity MCP` output channel logs -If the problem involves Unity launching, also include: +If the problem involves Unity launch or Unity closing unexpectedly, also capture: - Unity editor version -- whether the issue happened on plain launch or MCP-connected launch +- whether the launch was plain or MCP-connected - whether it was the first Unity open after installing the package +- Unity `Editor.log` + +On macOS, Unity `Editor.log` is usually: + +```text +~/Library/Logs/Unity/Editor.log +``` + +## Extension Logs + +Open the logs with: + +- `Unity MCP: Show Output` + +Increase detail with the VS Code setting: + +- `unityMcp.logLevel = debug` + +Important log groups: + +- `activate:*` + extension activation and command registration +- `status:*` + workspace inspection and diagnostics +- `dashboard:*` + dashboard resolve, render, refresh, and button routing +- `configure:*` + `.vscode/mcp.json` generation and write results +- `pluginInstall:*` + `Packages/manifest.json` install flow +- `openUnity:*` + Unity launch mode, warnings, and user-facing fallbacks +- `cliAdapter:*` + calls into the shared `unity-mcp-cli` implementation + +## Common Problems + +### The Dashboard Is Empty Or Missing + +Check: + +1. The `Unity MCP` Activity Bar icon exists. +2. `Unity MCP: Show Output` shows `dashboard:viewResolved`. +3. The output also shows `dashboard:render`. + +If the icon exists but the dashboard still does not render, reload the VS Code window and retry. Capture the `dashboard:*` log lines. + +### The Workspace Is Read-Only + +If the extension refuses to install, configure, or launch Unity, the workspace is probably not trusted. Trust the workspace in VS Code and retry. + +Typical log hint: + +- `workspace-not-trusted` + +### Install Plugin Worked But Connected Launch Does Not + +This usually means Unity has not created `UserSettings/AI-Game-Developer-Config.json` yet. + +Do this: + +1. Open Unity once without MCP. +2. Let package import and compilation finish. +3. Retry `Open Unity With MCP`. + +Typical log hint: + +- `project-config-missing` + +### Configure Project Worked But MCP Is Still Not Ready + +Inspect: + +- `.vscode/mcp.json` +- `UserSettings/AI-Game-Developer-Config.json` +- the `Unity MCP Status` report in the output channel + +The most common causes are: + +- Unity project config not initialized yet +- stale workspace state before a refresh +- editing the generated config manually into an invalid state + +Run `Unity MCP: Check Status` again after refreshing or reopening the workspace. + +### Unity Closed Unexpectedly During Setup + +This extension should not need Unity to stay open while writing `.vscode/mcp.json`, so an editor close during `Configure Project` is suspicious and worth reporting if it repeats. + +If it happens again: + +1. Capture the `Unity MCP` output channel logs. +2. Capture Unity `Editor.log`. +3. Note the exact action: + - `Install Plugin` + - `Configure Project` + - `Open Unity` + - `Open Unity With MCP` +4. Note whether Unity had just finished importing the package. + +If it only happens once and the project then behaves normally, keep the note but do not assume the extension caused it directly. + +## Files To Inspect Manually + +If you need to verify state by hand, check: + +- `Packages/manifest.json` +- `.vscode/mcp.json` +- `UserSettings/AI-Game-Developer-Config.json` + +## Reporting A Bug + +Open an issue in the main repository: + +- [Unity-MCP Issues](https://github.com/IvanMurzak/Unity-MCP/issues) + +A strong bug report includes: -If Unity closes unexpectedly during setup, reopen the project and retry once. If the issue repeats, capture Unity `Editor.log` and file a bug with the exact step that triggered it. +- exact repro steps +- exact command or dashboard button used +- output channel logs +- Unity `Editor.log` if Unity was involved +- whether the failure reproduces after reloading VS Code or reopening Unity From bb28e400ded60b8dc45f56340c333d639821530b Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 13:55:29 -0500 Subject: [PATCH 10/12] chore: remove migrated codex metadata --- .agents/skills/build-cli/SKILL.md | 45 ---- .agents/skills/github-pr-review-fix/SKILL.md | 212 ------------------- .codex/migrate-to-codex-report.txt | 17 -- AGENTS.md | 22 -- 4 files changed, 296 deletions(-) delete mode 100644 .agents/skills/build-cli/SKILL.md delete mode 100644 .agents/skills/github-pr-review-fix/SKILL.md delete mode 100644 .codex/migrate-to-codex-report.txt delete mode 100644 AGENTS.md diff --git a/.agents/skills/build-cli/SKILL.md b/.agents/skills/build-cli/SKILL.md deleted file mode 100644 index ac8dd290b..000000000 --- a/.agents/skills/build-cli/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: "build-cli" -description: "Build the unity-mcp-cli TypeScript CLI tool and link it globally for terminal use." ---- - -# Build CLI - -Build the `unity-mcp-cli` TypeScript project and optionally link it globally. -If the user includes `--link` or explicitly asks for a global link, run the optional link step. - -## Step 1 — Install Dependencies - -```bash -cd cli && npm install -``` - -Only needed if `node_modules/` is missing or `package.json` changed. - -## Step 2 — Build TypeScript - -```bash -cd cli && npm run build -``` - -This compiles `src/**/*.ts` → `dist/**/*.js` via `tsc`. - -## Step 3 — Link Globally (only when requested or for first-time setup) - -```bash -cd cli && npm link -``` - -This creates a global symlink so `unity-mcp-cli` is available from any terminal. Only needed once — after that, `npm run build` alone is sufficient. - -## Step 4 — Verify - -```bash -unity-mcp-cli --version -``` - -Confirm the CLI is accessible and shows the expected version. - -## Report - -Print the version and confirm success. If any step failed, show the error output. diff --git a/.agents/skills/github-pr-review-fix/SKILL.md b/.agents/skills/github-pr-review-fix/SKILL.md deleted file mode 100644 index 36b0b351e..000000000 --- a/.agents/skills/github-pr-review-fix/SKILL.md +++ /dev/null @@ -1,212 +0,0 @@ ---- -name: "github-pr-review-fix" -description: "Review and resolve PR comments from GitHub. Validates each comment, fixes legitimate issues." ---- - -# Review PR Comments - -Review unresolved comments on the GitHub pull request associated with the current branch. Validate each comment, then fix legitimate issues. -If the user includes a PR number, use that PR instead of inferring one from the current branch. - -## Step 1 — Identify the Pull Request - -1. If the user supplied a PR number, use it. -2. Otherwise, detect the current git branch and find its open PR: - ```bash - gh pr view --json number,url,headRefName,baseRefName - ``` -3. If no PR is found, stop and tell the user. - -## Step 2 — Fetch All Unresolved Review Comments - -Fetch PR review comments (not issue-level comments) using the GitHub CLI: - -```bash -gh api repos/{owner}/{repo}/pulls/{number}/comments --paginate -``` - -Filter to comments where the thread is **not resolved**. Group comments by thread (same `in_reply_to_id` or same `path` + `line`/`original_line`). For each thread, treat the **first comment** as the review request and subsequent comments as discussion. - -Also fetch general PR comments (issue-level): -```bash -gh api repos/{owner}/{repo}/issues/{number}/comments --paginate -``` - -If there are zero unresolved comments, report that and stop. - -## Step 3 — Validate and Fix Comments in Parallel (Sub-agents) - -For **each** unresolved comment or comment thread, spawn a **sub-agent** in parallel. If the runtime allows explicit model selection, prefer a smaller cost-efficient coding model. Each agent validates the comment and, if legitimate, fixes it immediately in place. - -**Conflict avoidance**: If multiple comments target the **same file**, run those agents **sequentially** (not in parallel) to avoid edit conflicts. Comments on **different files** run in parallel. - -### Sub-agent Prompt Template - -``` -You are a code review comment validator and fixer. Your job is to determine whether a PR review comment identifies a real issue, and if so, fix it immediately. - -## Comment Details -- **Author**: {comment_author} -- **File**: {file_path} -- **Line(s)**: {line_range} -- **Comment**: {comment_body} -- **Thread context** (if any): {thread_replies} -- **Comment ID**: {comment_id} - -## Phase 1 — Validate - -1. Read the file referenced in the comment. Focus on the specific lines mentioned. -2. Read surrounding context (50 lines above and below) to understand the code fully. -3. Analyze whether the comment identifies a legitimate issue: - - Is the described problem actually present in the code? - - Is the suggestion an improvement or just a style preference? - - Does the comment apply to the current state of the code (it may already be fixed)? - - Is this a nitpick / optional suggestion vs. a real bug, logic error, or missing handling? - -4. Decide: FIX or IGNORE. - - If IGNORE, skip to the report below. - - If FIX, proceed to Phase 2. - -## Phase 2 — Fix (only if verdict is FIX) - -1. Apply the minimal fix that addresses the comment. Do not refactor unrelated code. -2. Ensure the fix: - - Does not break surrounding logic - - Follows the existing code style and conventions - - Preserves all existing functionality -3. If the fix requires changes in multiple locations within the same file, make all changes. -4. If you are unsure whether a fix is safe, do NOT apply it — set verdict to NEEDS_MANUAL_REVIEW instead. - -HARD CONSTRAINTS: -- Only modify the file(s) specified. Do not touch other files. -- Do not add comments explaining the fix in the code. -- Do not refactor or "improve" code beyond what the comment asks for. -- Keep changes minimal and surgical. - -## Report - -Return your result in this exact format: - -VERDICT: FIX | IGNORE | NEEDS_MANUAL_REVIEW -CONFIDENCE: HIGH | MEDIUM | LOW -REASON: <1-2 sentence explanation> -SUMMARY: <1 sentence describing what was changed, only if VERDICT is FIX> -FILE: {file_path} -LINES: {line_range} -COMMENT_ID: {comment_id} -FIXED: YES | NO -``` - -## Step 4 — Resolve Fixed Comments on GitHub - -After a fix sub-agent successfully fixes an issue, **resolve the corresponding review thread on GitHub** using the GraphQL API. - -1. First, find the thread ID for the comment. Use the `node_id` from the comment (fetched in Step 2) to query the thread: - ```bash - gh api graphql -f query=' - query { - node(id: "{comment_node_id}") { - ... on PullRequestReviewComment { - pullRequestReview { - id - } - id - isMinimized - } - } - } - ' - ``` - -2. Resolve the review thread using the `resolveReviewThread` mutation. The thread ID can be obtained from the pull request's review threads: - ```bash - gh api graphql -f query=' - query { - repository(owner: "{owner}", name: "{repo}") { - pullRequest(number: {number}) { - reviewThreads(first: 100) { - nodes { - id - isResolved - comments(first: 1) { - nodes { - id - body - path - line - } - } - } - } - } - } - } - ' - ``` - Match the thread by comparing `path` and `line` (or `body`) to the fixed comment, then resolve it: - ```bash - gh api graphql -f query=' - mutation { - resolveReviewThread(input: {threadId: "{thread_id}"}) { - thread { - id - isResolved - } - } - } - ' - ``` - -3. For every successfully fixed comment, reply to the thread before resolving it: - ```bash - gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies \ - -f body="## Agentic reply (github-pr-review-fix) - -Fixed. {summary} - -_If you disagree with the fix, please reopen this thread._" - ``` - Then resolve the thread. If the resolve call fails, note it in the report but do not block on it. - -4. For **IGNORE** verdicts, reply to the comment thread with an explanation of why it was ignored, then resolve the thread: - ```bash - gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies \ - -f body="## Agentic reply (github-pr-review-fix) - -This comment was reviewed and determined to not require a code change. - -**Reason**: {reason} - -_If you disagree, please reopen this thread._" - ``` - Then resolve the thread using the same `resolveReviewThread` mutation as above. - -5. For **NEEDS_MANUAL_REVIEW** verdicts, reply to the thread but do **not** resolve it: - ```bash - gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies \ - -f body="## Agentic reply (github-pr-review-fix) - -This comment requires manual review — the agent could not safely apply an automated fix. - -**Reason**: {reason} - -_Leaving this thread unresolved for human attention._" - ``` - -## Step 5 — Report Results - -After all agents complete, provide a summary table: - -``` -| # | File | Comment | Verdict | Reason | Resolved | -|---|------|---------|---------|--------|----------| -| 1 | path/to/file.cs:42 | "Missing null check" | FIX | Added null guard | Yes | -| 2 | path/to/other.cs:10 | "Consider renaming" | IGNORE | Style preference, no bug | Yes (replied) | -| 3 | path/to/util.cs:77 | "Race condition" | NEEDS_MANUAL_REVIEW | Unsafe to auto-fix, needs human judgment | — | -``` - -For **IGNORE** verdicts, the reason is posted as a reply in the thread and the thread is resolved. The user can reopen if they disagree. - -For **NEEDS_MANUAL_REVIEW** verdicts, explain why the agent couldn't safely apply a fix. These threads are left **unresolved** for human attention. - -End with a count: `X comments fixed and resolved, Y ignored and resolved, Z need manual review.` diff --git a/.codex/migrate-to-codex-report.txt b/.codex/migrate-to-codex-report.txt deleted file mode 100644 index 3d366b4aa..000000000 --- a/.codex/migrate-to-codex-report.txt +++ /dev/null @@ -1,17 +0,0 @@ -Migration inventory: - inactive: instruction files - none found - active: skills - 2 found - - build-cli - - github-pr-review-fix - inactive: command sources - none found - inactive: subagents - none found -Migration surfaces: - inactive: AGENTS.md - No supported instruction file found. - active: skills - 2 skill(s) converted. - inactive: MCP config - No settings or MCP config found. - inactive: subagents - No subagents found. -Migration report: - manual_fix_required: .agents/skills/build-cli/SKILL.md - Manual review required for Claude skill fields: `argument-hint`, `disable-model-invocation`. - manual_fix_required: .agents/skills/github-pr-review-fix/SKILL.md - Manual review required for Claude skill fields: `argument-hint`, `disable-model-invocation`. - overwritten: /Users/suporte/Unity-MCP/.agents/skills/build-cli - Existing Codex skill will be replaced. - overwritten: /Users/suporte/Unity-MCP/.agents/skills/github-pr-review-fix - Existing Codex skill will be replaced. diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 424abd26b..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,22 +0,0 @@ -# AGENTS.md - -## What This Is - -Unity-MCP bridges LLMs (Codex, Cursor, Copilot, etc.) with Unity Editor and Runtime via the [Model Context Protocol](https://modelcontextprotocol.io/). Sub-projects: `Unity-MCP-Server/` (ASP.NET Core + MCP SDK), `Unity-MCP-Plugin/` (Unity Editor/Runtime plugin), `cli/`, `Installer/`, `Unity-Tests/`. - -## Build / Run - -- Bump version: `.\commands\bump-version.ps1 ` -- CI/CD pipelines live in `.github/workflows/`. - -## Project Constitution - -Non-negotiable principles and architecture constraints: [`.specify/memory/constitution.md`](.specify/memory/constitution.md). You MUST read the constitution before performing any code review. - -## Find Detail In - -- [docs/Codex/architecture.md](docs/Codex/architecture.md) — System architecture: SignalR bridge, main-thread execution, deterministic port hashing -- [docs/Codex/style.md](docs/Codex/style.md) — Coding conventions: `#nullable enable`, no reflection for private access, namespace pattern, copyright headers -- [docs/Codex/release.md](docs/Codex/release.md) — Release, versioning, CI/CD -- [docs/Codex/documentation-sync.md](docs/Codex/documentation-sync.md) — README translation/copy sync requirements -- `Unity-MCP-Server/AGENTS.md`, `Unity-MCP-Plugin/AGENTS.md` — sub-project specifics From cc14a86a3eed701dac4a6dd15387e9803bd6985c Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Thu, 4 Jun 2026 17:39:06 -0500 Subject: [PATCH 11/12] fix: address vscode extension review feedback --- vscode-extension/DEVELOPMENT.md | 4 +- vscode-extension/PUBLISHING.md | 2 +- vscode-extension/README.md | 2 +- vscode-extension/src/dashboard.ts | 8 +-- vscode-extension/src/extension.ts | 79 ++++++++++++++++++++-- vscode-extension/src/projectStatus.test.ts | 44 ++++++++++++ vscode-extension/src/projectStatus.ts | 31 +++++---- vscode-extension/src/unityConfig.ts | 16 +---- vscode-extension/src/utils.ts | 14 ++++ 9 files changed, 157 insertions(+), 43 deletions(-) create mode 100644 vscode-extension/src/utils.ts diff --git a/vscode-extension/DEVELOPMENT.md b/vscode-extension/DEVELOPMENT.md index e11b47432..4fde39518 100644 --- a/vscode-extension/DEVELOPMENT.md +++ b/vscode-extension/DEVELOPMENT.md @@ -4,7 +4,7 @@ This guide is for maintainers changing the VS Code extension itself. ## Local Workflow -Run from `/Users/suporte/Unity-MCP/vscode-extension`: +Run from the `vscode-extension/` directory: ```bash npm install @@ -14,7 +14,7 @@ npm test To debug the extension host: -1. Open `/Users/suporte/Unity-MCP/vscode-extension` in VS Code. +1. Open the `vscode-extension/` folder in VS Code. 2. Press `F5`. 3. In the Extension Development Host, open a Unity project. 4. Use the dashboard, status bar, or command palette. diff --git a/vscode-extension/PUBLISHING.md b/vscode-extension/PUBLISHING.md index 1513d1341..8d106a032 100644 --- a/vscode-extension/PUBLISHING.md +++ b/vscode-extension/PUBLISHING.md @@ -13,7 +13,7 @@ This extension is ready for local VSIX packaging and maintainer handoff, but it ## Verification Checklist -Run from `/Users/suporte/Unity-MCP/vscode-extension`: +Run from the `vscode-extension/` directory: ```bash npm install diff --git a/vscode-extension/README.md b/vscode-extension/README.md index 0a1f12a9b..81816cb08 100644 --- a/vscode-extension/README.md +++ b/vscode-extension/README.md @@ -135,7 +135,7 @@ npm run build npm test ``` -Then open `/Users/suporte/Unity-MCP/vscode-extension` in VS Code and press `F5` to launch an Extension Development Host. +Then open the `vscode-extension/` folder in VS Code and press `F5` to launch an Extension Development Host. Detailed maintainer guidance is in: diff --git a/vscode-extension/src/dashboard.ts b/vscode-extension/src/dashboard.ts index b6c3d0a24..2310f0b54 100644 --- a/vscode-extension/src/dashboard.ts +++ b/vscode-extension/src/dashboard.ts @@ -1,3 +1,4 @@ +import { randomBytes } from 'node:crypto'; import * as vscode from 'vscode'; import { formatWorkspaceStatusReport, @@ -569,12 +570,7 @@ function dedupeActions(items: DashboardActionItem[]): DashboardActionItem[] { } function createNonce(): string { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - for (let index = 0; index < 24; index += 1) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; + return randomBytes(18).toString('base64url'); } function escapeHtml(value: string): string { diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index 18960eb11..9f7a5a0bd 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -14,6 +14,13 @@ import { import { readUnityMcpProjectConfig } from './unityConfig'; import { getPreferredWorkspaceFolder, pickWorkspaceFolder } from './workspace'; +const UI_REFRESH_DEBOUNCE_MS = 150; +const STATUS_RELEVANT_FILES = new Set([ + 'Packages/manifest.json', + '.vscode/mcp.json', + 'UserSettings/AI-Game-Developer-Config.json', +]); + export async function activate(context: vscode.ExtensionContext): Promise { const logger = new ExtensionLogger(); context.subscriptions.push(logger); @@ -44,6 +51,8 @@ export async function activate(context: vscode.ExtensionContext): Promise trusted: vscode.workspace.isTrusted, }); + let scheduledRefreshHandle: NodeJS.Timeout | undefined; + async function getDashboardSnapshot(): Promise { const workspaceFolder = getPreferredWorkspaceFolder(); if (!workspaceFolder) { @@ -81,6 +90,36 @@ export async function activate(context: vscode.ExtensionContext): Promise await dashboardProvider.refresh(); } + function scheduleRefreshUi(reason: string): void { + if (scheduledRefreshHandle) { + clearTimeout(scheduledRefreshHandle); + } + + logger.debug('dashboard:refreshScheduled', { + reason, + delayMs: UI_REFRESH_DEBOUNCE_MS, + }); + + scheduledRefreshHandle = setTimeout(() => { + scheduledRefreshHandle = undefined; + void refreshUi(); + }, UI_REFRESH_DEBOUNCE_MS); + } + + function isRelevantStatusDocument(document: vscode.TextDocument): boolean { + if (document.uri.scheme !== 'file') { + return false; + } + + const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri); + if (!workspaceFolder) { + return false; + } + + const relativePath = pathRelativeToWorkspace(workspaceFolder, document.uri); + return STATUS_RELEVANT_FILES.has(relativePath); + } + async function runOpenUnity( mode: 'prompt' | 'plain' | 'connected', ): Promise { @@ -250,28 +289,40 @@ export async function activate(context: vscode.ExtensionContext): Promise context.subscriptions.push( vscode.workspace.onDidGrantWorkspaceTrust(() => { logger.info('trust:granted', {}); - void refreshUi(); + scheduleRefreshUi('workspace-trusted'); }), ); context.subscriptions.push( vscode.workspace.onDidChangeWorkspaceFolders(() => { - void refreshUi(); + scheduleRefreshUi('workspace-folders-changed'); }), ); context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor(() => { - void refreshUi(); + scheduleRefreshUi('active-editor-changed'); }), ); context.subscriptions.push( - vscode.workspace.onDidSaveTextDocument(() => { - void refreshUi(); + vscode.workspace.onDidSaveTextDocument((document) => { + if (!isRelevantStatusDocument(document)) { + return; + } + + scheduleRefreshUi(`saved:${document.uri.fsPath}`); }), ); + context.subscriptions.push({ + dispose: () => { + if (scheduledRefreshHandle) { + clearTimeout(scheduledRefreshHandle); + } + }, + }); + context.subscriptions.push( vscode.commands.registerCommand('unityMcp.showOutput', () => { logger.show(); @@ -754,3 +805,21 @@ function statusActionToCommand(actionLabel: string): string | undefined { export function deactivate(): void { // Nothing to dispose beyond the extension context subscriptions. } + +function pathRelativeToWorkspace( + workspaceFolder: vscode.WorkspaceFolder, + uri: vscode.Uri, +): string { + const normalizedWorkspace = normalizeFsPath(workspaceFolder.uri.fsPath); + const normalizedDocument = normalizeFsPath(uri.fsPath); + + if (!normalizedDocument.startsWith(`${normalizedWorkspace}/`)) { + return normalizedDocument; + } + + return normalizedDocument.slice(normalizedWorkspace.length + 1); +} + +function normalizeFsPath(targetPath: string): string { + return targetPath.replaceAll('\\', '/'); +} diff --git a/vscode-extension/src/projectStatus.test.ts b/vscode-extension/src/projectStatus.test.ts index 254417c83..00a2bc357 100644 --- a/vscode-extension/src/projectStatus.test.ts +++ b/vscode-extension/src/projectStatus.test.ts @@ -105,6 +105,50 @@ describe('inspectWorkspaceStatus', () => { expect(status.warnings.some((warning) => warning.includes('Could not parse .vscode/mcp.json'))).toBe(true); expect(status.recommendedActions).toEqual(['open-unity-without-mcp']); }); + + it('treats an incomplete ai-game-developer MCP entry as not configured', async () => { + const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, 'Assets'), { recursive: true }); + await mkdir(path.join(workspace, 'ProjectSettings'), { recursive: true }); + await mkdir(path.join(workspace, 'Packages'), { recursive: true }); + await mkdir(path.join(workspace, 'UserSettings'), { recursive: true }); + await mkdir(path.join(workspace, '.vscode'), { recursive: true }); + await writeFile( + path.join(workspace, 'Packages', 'manifest.json'), + JSON.stringify({ + dependencies: { + 'com.ivanmurzak.unity.mcp': '0.79.0', + }, + }, null, 2), + ); + await writeFile( + path.join(workspace, 'UserSettings', 'AI-Game-Developer-Config.json'), + JSON.stringify({ + host: 'http://localhost:6501', + authOption: 'none', + transportMethod: 'streamableHttp', + }, null, 2), + ); + await writeFile( + path.join(workspace, '.vscode', 'mcp.json'), + JSON.stringify({ + servers: { + 'ai-game-developer': {}, + }, + }, null, 2), + ); + + const status = await inspectWorkspaceStatus(workspace, 'IncompleteConfig', 'trusted'); + + expect(status.mcpConfigExists).toBe(true); + expect(status.mcpServerConfigured).toBe(false); + expect(status.mcpServerTransport).toBeUndefined(); + expect( + status.warnings.some((warning) => + warning.includes('missing a supported transport type')), + ).toBe(true); + expect(status.recommendedActions).toEqual(['configure-vscode-mcp', 'open-unity-with-mcp']); + }); }); async function createTempWorkspace(): Promise { diff --git a/vscode-extension/src/projectStatus.ts b/vscode-extension/src/projectStatus.ts index c7c325037..8a325b510 100644 --- a/vscode-extension/src/projectStatus.ts +++ b/vscode-extension/src/projectStatus.ts @@ -1,9 +1,11 @@ import { promises as fs } from 'node:fs'; import * as path from 'node:path'; import { readUnityMcpProjectConfig } from './unityConfig'; +import { pathExists, toErrorMessage } from './utils'; const MCP_SERVER_NAME = 'ai-game-developer'; const UNITY_MCP_PACKAGE_NAME = 'com.ivanmurzak.unity.mcp'; +const VALID_VSCODE_MCP_TRANSPORTS = new Set(['http', 'stdio']); export interface WorkspaceStatus { workspaceName: string; @@ -231,12 +233,20 @@ async function readMcpConfig(mcpConfigPath: string): Promise { }; const serverEntry = parsed.servers?.[MCP_SERVER_NAME]; + const transport = parseMcpTransport(serverEntry?.type); + const warnings: string[] = []; + + if (serverEntry && transport === undefined) { + warnings.push( + `The ai-game-developer server entry in .vscode/mcp.json is missing a supported transport type. Expected one of: ${Array.from(VALID_VSCODE_MCP_TRANSPORTS).join(', ')}.`, + ); + } return { exists: true, - hasServerEntry: Boolean(serverEntry), - transport: serverEntry?.type, - warnings: [], + hasServerEntry: transport !== undefined, + transport, + warnings, }; } catch (error) { return { @@ -249,15 +259,8 @@ async function readMcpConfig(mcpConfigPath: string): Promise { } } -async function pathExists(targetPath: string): Promise { - try { - await fs.access(targetPath); - return true; - } catch { - return false; - } -} - -function toErrorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); +function parseMcpTransport(value: unknown): string | undefined { + return typeof value === 'string' && VALID_VSCODE_MCP_TRANSPORTS.has(value) + ? value + : undefined; } diff --git a/vscode-extension/src/unityConfig.ts b/vscode-extension/src/unityConfig.ts index bbe5e4281..ab9775d04 100644 --- a/vscode-extension/src/unityConfig.ts +++ b/vscode-extension/src/unityConfig.ts @@ -1,5 +1,6 @@ -import { promises as fs } from 'node:fs'; import * as path from 'node:path'; +import { promises as fs } from 'node:fs'; +import { pathExists, toErrorMessage } from './utils'; export interface UnityMcpProjectConfig { exists: boolean; @@ -52,15 +53,6 @@ export async function readUnityMcpProjectConfig( } } -async function pathExists(targetPath: string): Promise { - try { - await fs.access(targetPath); - return true; - } catch { - return false; - } -} - function parseAuthOption(value: unknown): 'none' | 'required' | undefined { return value === 'none' || value === 'required' ? value : undefined; } @@ -68,7 +60,3 @@ function parseAuthOption(value: unknown): 'none' | 'required' | undefined { function parseTransport(value: unknown): 'streamableHttp' | 'stdio' | undefined { return value === 'stdio' || value === 'streamableHttp' ? value : undefined; } - -function toErrorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} diff --git a/vscode-extension/src/utils.ts b/vscode-extension/src/utils.ts new file mode 100644 index 000000000..ead435222 --- /dev/null +++ b/vscode-extension/src/utils.ts @@ -0,0 +1,14 @@ +import { promises as fs } from 'node:fs'; + +export async function pathExists(targetPath: string): Promise { + try { + await fs.access(targetPath); + return true; + } catch { + return false; + } +} + +export function toErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} From 9d1b7ba1efea2630376a6db962f205b0ed9aee9a Mon Sep 17 00:00:00 2001 From: Felipe de Lima Date: Fri, 5 Jun 2026 04:22:38 -0500 Subject: [PATCH 12/12] fix: harden vscode extension config handling --- vscode-extension/src/cliAdapter.ts | 16 +-- vscode-extension/src/dashboard.test.ts | 20 ++++ vscode-extension/src/dashboard.ts | 25 ++++- vscode-extension/src/extension.ts | 28 +++-- vscode-extension/src/projectStatus.test.ts | 37 +++++++ vscode-extension/src/projectStatus.ts | 13 ++- vscode-extension/src/unityConfig.test.ts | 31 +++++- vscode-extension/src/unityConfig.ts | 122 ++++++++++++++++++++- vscode-extension/tsconfig.json | 4 +- 9 files changed, 264 insertions(+), 32 deletions(-) diff --git a/vscode-extension/src/cliAdapter.ts b/vscode-extension/src/cliAdapter.ts index 2ceed55e5..a7b1d1fef 100644 --- a/vscode-extension/src/cliAdapter.ts +++ b/vscode-extension/src/cliAdapter.ts @@ -6,13 +6,11 @@ import type { ProgressEvent, SetupMcpOptions, SetupMcpResult, -} from 'unity-mcp-cli'; +} from 'unity-mcp-cli' with { "resolution-mode": "import" }; import { ExtensionLogger } from './logging'; export type CliModuleLoader = () => Promise; -type NativeImport = (specifier: string) => Promise; - interface UnityMcpCliModule { installPlugin(options: InstallPluginOptions): Promise; openProject(options: OpenProjectOptions): Promise; @@ -225,17 +223,11 @@ export async function openUnityProject( } async function defaultLoader(): Promise { - // Keep native ESM loading at runtime. TypeScript rewrites plain - // `import()` into `require()` for CommonJS output, which breaks on - // packages like `unity-mcp-cli` that only expose ESM import exports. - return nativeImport('unity-mcp-cli'); + // tsconfig uses the Node16 module target so this CommonJS extension + // entrypoint keeps native import() at runtime for ESM-only packages. + return import('unity-mcp-cli') as Promise; } -const nativeImport = new Function( - 'specifier', - 'return import(specifier);', -) as NativeImport; - function toErrorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } diff --git a/vscode-extension/src/dashboard.test.ts b/vscode-extension/src/dashboard.test.ts index 85b48c0b3..90bb5bc6c 100644 --- a/vscode-extension/src/dashboard.test.ts +++ b/vscode-extension/src/dashboard.test.ts @@ -15,6 +15,7 @@ describe('buildStatusBarPresentation', () => { createSnapshot({ pluginInstalled: false, unityMcpProjectConfigExists: false, + unityMcpProjectConfigReady: false, mcpServerConfigured: false, }), ); @@ -27,6 +28,7 @@ describe('buildStatusBarPresentation', () => { createSnapshot({ pluginInstalled: true, unityMcpProjectConfigExists: false, + unityMcpProjectConfigReady: false, mcpServerConfigured: false, }), ); @@ -39,6 +41,7 @@ describe('buildStatusBarPresentation', () => { createSnapshot({ pluginInstalled: true, unityMcpProjectConfigExists: true, + unityMcpProjectConfigReady: true, mcpServerConfigured: true, }), ); @@ -68,6 +71,7 @@ describe('buildDashboardActions', () => { createSnapshot({ pluginInstalled: true, unityMcpProjectConfigExists: true, + unityMcpProjectConfigReady: true, mcpServerConfigured: true, recommendedActions: ['open-unity-with-mcp'], }), @@ -82,6 +86,7 @@ describe('buildDashboardActions', () => { createSnapshot({ pluginInstalled: true, unityMcpProjectConfigExists: true, + unityMcpProjectConfigReady: true, mcpServerConfigured: false, recommendedActions: ['configure-vscode-mcp', 'open-unity-with-mcp'], }), @@ -89,6 +94,20 @@ describe('buildDashboardActions', () => { expect(actions[0]?.commandId).toBe('unityMcp.configureProject'); }); + + it('shows a fix-config state when the Unity config file exists but is not ready', () => { + const presentation = buildStatusBarPresentation( + createSnapshot({ + pluginInstalled: true, + unityMcpProjectConfigExists: true, + unityMcpProjectConfigReady: false, + mcpServerConfigured: false, + recommendedActions: ['open-unity-without-mcp'], + }), + ); + + expect(presentation.text).toBe('$(warning) Unity MCP: Fix Config'); + }); }); function createSnapshot(overrides: Partial): DashboardSnapshot { @@ -112,6 +131,7 @@ function createStatus(overrides: Partial): WorkspaceStatus { pluginInstalled: true, pluginVersion: '0.79.0', unityMcpProjectConfigExists: true, + unityMcpProjectConfigReady: true, mcpConfigExists: true, mcpServerConfigured: true, mcpServerTransport: 'http', diff --git a/vscode-extension/src/dashboard.ts b/vscode-extension/src/dashboard.ts index 2310f0b54..c690fb0c7 100644 --- a/vscode-extension/src/dashboard.ts +++ b/vscode-extension/src/dashboard.ts @@ -84,7 +84,7 @@ export class UnityMcpDashboardProvider implements vscode.WebviewViewProvider { trustState: snapshot.status?.trustState, unityProjectDetected: snapshot.status?.unityProjectDetected, pluginInstalled: snapshot.status?.pluginInstalled, - unityConfigReady: snapshot.status?.unityMcpProjectConfigExists, + unityConfigReady: snapshot.status?.unityMcpProjectConfigReady, mcpConfigured: snapshot.status?.mcpServerConfigured, }); this.view.webview.html = renderDashboardHtml( @@ -136,6 +136,13 @@ export function buildStatusBarPresentation(snapshot: DashboardSnapshot): { }; } + if (!status.unityMcpProjectConfigReady) { + return { + text: '$(warning) Unity MCP: Fix Config', + tooltip: 'The Unity MCP project config exists but is invalid or incomplete. Open Unity without MCP and review the diagnostics.', + }; + } + if (!status.mcpServerConfigured) { return { text: '$(settings-gear) Unity MCP: Configure', @@ -237,6 +244,13 @@ export function buildDashboardActions(snapshot: DashboardSnapshot): DashboardAct description: 'Re-run package installation to reconcile Packages/manifest.json if needed.', recommended: false, }); + } else if (!status.unityMcpProjectConfigReady) { + items.push({ + commandId: 'unityMcp.openUnityPlain', + label: 'Open Unity', + description: 'Launch Unity without MCP and repair or regenerate the AI-Game-Developer project config.', + recommended: true, + }); } else if (!status.mcpServerConfigured) { items.push({ commandId: 'unityMcp.configureProject', @@ -463,7 +477,7 @@ function renderDashboardHtml(
Unity Config - ${status.unityMcpProjectConfigExists ? 'Ready' : 'Missing'} + ${!status.unityMcpProjectConfigExists ? 'Missing' : status.unityMcpProjectConfigReady ? 'Ready' : 'Needs attention'}
VS Code MCP @@ -536,6 +550,13 @@ function buildDashboardRecommendation( detail: 'Add the Unity package to Packages/manifest.json before trying to launch or connect with MCP.', }; case 'open-unity-without-mcp': + if (status.unityMcpProjectConfigExists) { + return { + title: 'Repair the Unity project config', + detail: 'Open Unity without MCP and fix or regenerate AI-Game-Developer-Config.json before retrying connected launch.', + }; + } + return { title: 'Initialize the Unity project', detail: 'Open Unity once without MCP so the newly installed package can import and create its project config.', diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index 9f7a5a0bd..9a73a57cf 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -80,7 +80,7 @@ export async function activate(context: vscode.ExtensionContext): Promise trustState: snapshot.status?.trustState, unityProjectDetected: snapshot.status?.unityProjectDetected, pluginInstalled: snapshot.status?.pluginInstalled, - unityConfigReady: snapshot.status?.unityMcpProjectConfigExists, + unityConfigReady: snapshot.status?.unityMcpProjectConfigReady, mcpConfigured: snapshot.status?.mcpServerConfigured, }); const statusBar = buildStatusBarPresentation(snapshot); @@ -191,9 +191,7 @@ export async function activate(context: vscode.ExtensionContext): Promise }, { label: 'Open Unity With MCP Connection', - detail: projectConfig.exists - ? 'Use the current AI-Game-Developer project config and request server startup.' - : 'Requires UserSettings/AI-Game-Developer-Config.json to be present.', + detail: describeConnectedLaunchAvailability(projectConfig), mode: 'connected' as const, }, ], @@ -213,14 +211,16 @@ export async function activate(context: vscode.ExtensionContext): Promise effectiveMode = openMode.mode; } - if (effectiveMode === 'connected' && !projectConfig.exists) { + if (effectiveMode === 'connected' && !projectConfig.ready) { logger.warn('openUnity:precheck', { workspace: workspaceFolder.uri.fsPath, - reason: 'project-config-missing', + reason: projectConfig.exists ? 'project-config-invalid' : 'project-config-missing', }); const selection = await vscode.window.showWarningMessage( - 'Unity MCP is installed, but the project has not finished first-time initialization yet. Open Unity once without MCP so the package can import and create its project config, then retry connected launch.', + projectConfig.exists + ? 'Unity MCP found AI-Game-Developer-Config.json, but it is invalid or incomplete. Open Unity once without MCP and fix or regenerate the project config before retrying connected launch.' + : 'Unity MCP is installed, but the project has not finished first-time initialization yet. Open Unity once without MCP so the package can import and create its project config, then retry connected launch.', 'Open Without MCP', 'Show Output', 'Cancel', @@ -806,6 +806,20 @@ export function deactivate(): void { // Nothing to dispose beyond the extension context subscriptions. } +function describeConnectedLaunchAvailability( + projectConfig: Awaited>, +): string { + if (projectConfig.ready) { + return 'Use the current AI-Game-Developer project config and request server startup.'; + } + + if (projectConfig.exists) { + return 'Blocked until UserSettings/AI-Game-Developer-Config.json is valid again.'; + } + + return 'Requires UserSettings/AI-Game-Developer-Config.json to be present.'; +} + function pathRelativeToWorkspace( workspaceFolder: vscode.WorkspaceFolder, uri: vscode.Uri, diff --git a/vscode-extension/src/projectStatus.test.ts b/vscode-extension/src/projectStatus.test.ts index 00a2bc357..fd8c41c20 100644 --- a/vscode-extension/src/projectStatus.test.ts +++ b/vscode-extension/src/projectStatus.test.ts @@ -44,6 +44,7 @@ describe('inspectWorkspaceStatus', () => { expect(status.pluginInstalled).toBe(true); expect(status.pluginVersion).toBe('0.79.0'); expect(status.unityMcpProjectConfigExists).toBe(true); + expect(status.unityMcpProjectConfigReady).toBe(true); expect(status.warnings).toEqual([]); expect(status.recommendedActions).toEqual(['configure-vscode-mcp', 'open-unity-with-mcp']); }); @@ -78,6 +79,7 @@ describe('inspectWorkspaceStatus', () => { expect(status.pluginInstalled).toBe(true); expect(status.unityMcpProjectConfigExists).toBe(false); + expect(status.unityMcpProjectConfigReady).toBe(false); expect(status.warnings.some((warning) => warning.includes('Open Unity once without MCP'))).toBe(true); expect(status.recommendedActions).toEqual(['open-unity-without-mcp']); }); @@ -143,12 +145,47 @@ describe('inspectWorkspaceStatus', () => { expect(status.mcpConfigExists).toBe(true); expect(status.mcpServerConfigured).toBe(false); expect(status.mcpServerTransport).toBeUndefined(); + expect(status.unityMcpProjectConfigReady).toBe(true); expect( status.warnings.some((warning) => warning.includes('missing a supported transport type')), ).toBe(true); expect(status.recommendedActions).toEqual(['configure-vscode-mcp', 'open-unity-with-mcp']); }); + + it('treats a malformed Unity MCP project config as present but not ready', async () => { + const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, 'Assets'), { recursive: true }); + await mkdir(path.join(workspace, 'ProjectSettings'), { recursive: true }); + await mkdir(path.join(workspace, 'Packages'), { recursive: true }); + await mkdir(path.join(workspace, 'UserSettings'), { recursive: true }); + await writeFile( + path.join(workspace, 'Packages', 'manifest.json'), + JSON.stringify({ + dependencies: { + 'com.ivanmurzak.unity.mcp': '0.79.0', + }, + }, null, 2), + ); + await writeFile( + path.join(workspace, 'UserSettings', 'AI-Game-Developer-Config.json'), + '{broken json', + ); + + const status = await inspectWorkspaceStatus(workspace, 'BrokenUnityConfig', 'trusted'); + + expect(status.unityMcpProjectConfigExists).toBe(true); + expect(status.unityMcpProjectConfigReady).toBe(false); + expect( + status.warnings.some((warning) => + warning.includes('Could not parse UserSettings/AI-Game-Developer-Config.json')), + ).toBe(true); + expect( + status.warnings.some((warning) => + warning.includes('invalid or incomplete')), + ).toBe(true); + expect(status.recommendedActions).toEqual(['open-unity-without-mcp']); + }); }); async function createTempWorkspace(): Promise { diff --git a/vscode-extension/src/projectStatus.ts b/vscode-extension/src/projectStatus.ts index 8a325b510..f3fef5919 100644 --- a/vscode-extension/src/projectStatus.ts +++ b/vscode-extension/src/projectStatus.ts @@ -16,6 +16,7 @@ export interface WorkspaceStatus { pluginInstalled: boolean; pluginVersion?: string; unityMcpProjectConfigExists: boolean; + unityMcpProjectConfigReady: boolean; mcpConfigExists: boolean; mcpServerConfigured: boolean; mcpServerTransport?: string; @@ -70,6 +71,10 @@ export async function inspectWorkspaceStatus( warnings.push( 'Unity MCP project config is missing. Open Unity once without MCP after installing the plugin so the package can import and initialize.', ); + } else if (pluginInstalled && !projectConfig.ready) { + warnings.push( + 'Unity MCP project config is present but invalid or incomplete. Open Unity without MCP and fix or regenerate UserSettings/AI-Game-Developer-Config.json before retrying connected launch.', + ); } const mcpInfo = await readMcpConfig(mcpConfigPath); @@ -78,7 +83,7 @@ export async function inspectWorkspaceStatus( trustState, unityProjectDetected, pluginInstalled, - unityMcpProjectConfigExists: projectConfig.exists, + unityMcpProjectConfigReady: projectConfig.ready, mcpServerConfigured: mcpInfo.hasServerEntry, }); @@ -91,6 +96,7 @@ export async function inspectWorkspaceStatus( pluginInstalled, pluginVersion, unityMcpProjectConfigExists: projectConfig.exists, + unityMcpProjectConfigReady: projectConfig.ready, mcpConfigExists: mcpInfo.exists, mcpServerConfigured: mcpInfo.hasServerEntry, mcpServerTransport: mcpInfo.transport, @@ -108,6 +114,7 @@ export function formatWorkspaceStatusReport(status: WorkspaceStatus): string { `Unity markers: ${status.unityMarkers.length > 0 ? status.unityMarkers.join(', ') : 'none'}`, `Unity MCP plugin installed: ${status.pluginInstalled ? `yes (${status.pluginVersion ?? 'unknown version'})` : 'no'}`, `Unity MCP project config present: ${status.unityMcpProjectConfigExists ? 'yes' : 'no'}`, + `Unity MCP project config ready: ${status.unityMcpProjectConfigReady ? 'yes' : 'no'}`, `.vscode/mcp.json present: ${status.mcpConfigExists ? 'yes' : 'no'}`, `ai-game-developer configured: ${status.mcpServerConfigured ? 'yes' : 'no'}`, `Configured transport: ${status.mcpServerTransport ?? 'unknown'}`, @@ -134,7 +141,7 @@ function buildRecommendedActions(input: { trustState: 'trusted' | 'restricted'; unityProjectDetected: boolean; pluginInstalled: boolean; - unityMcpProjectConfigExists: boolean; + unityMcpProjectConfigReady: boolean; mcpServerConfigured: boolean; }): WorkspaceAction[] { if (input.trustState !== 'trusted') { @@ -152,7 +159,7 @@ function buildRecommendedActions(input: { return actions; } - if (!input.unityMcpProjectConfigExists) { + if (!input.unityMcpProjectConfigReady) { actions.push('open-unity-without-mcp'); return actions; } diff --git a/vscode-extension/src/unityConfig.test.ts b/vscode-extension/src/unityConfig.test.ts index f650a17ef..a75511c3f 100644 --- a/vscode-extension/src/unityConfig.test.ts +++ b/vscode-extension/src/unityConfig.test.ts @@ -30,6 +30,7 @@ describe('readUnityMcpProjectConfig', () => { const config = await readUnityMcpProjectConfig(workspace); expect(config.exists).toBe(true); + expect(config.ready).toBe(true); expect(config.host).toBe('http://localhost:6501'); expect(config.authOption).toBe('required'); expect(config.transport).toBe('streamableHttp'); @@ -47,7 +48,35 @@ describe('readUnityMcpProjectConfig', () => { const config = await readUnityMcpProjectConfig(workspace); expect(config.exists).toBe(true); - expect(config.warnings.some((warning) => warning.includes('Could not parse UserSettings/AI-Game-Developer-Config.json'))).toBe(true); + expect(config.ready).toBe(false); + expect( + config.warnings.some((warning) => + warning.includes('Could not parse UserSettings/AI-Game-Developer-Config.json')), + ).toBe(true); + }); + + it('marks the config as not ready when required connection fields are missing', async () => { + const workspace = await createTempWorkspace(); + await mkdir(path.join(workspace, 'UserSettings'), { recursive: true }); + await writeFile( + path.join(workspace, 'UserSettings', 'AI-Game-Developer-Config.json'), + JSON.stringify({ + authOption: 'none', + }), + ); + + const config = await readUnityMcpProjectConfig(workspace); + + expect(config.exists).toBe(true); + expect(config.ready).toBe(false); + expect( + config.warnings.some((warning) => + warning.includes('missing a supported transportMethod')), + ).toBe(true); + expect( + config.warnings.some((warning) => + warning.includes('missing a host URL')), + ).toBe(true); }); }); diff --git a/vscode-extension/src/unityConfig.ts b/vscode-extension/src/unityConfig.ts index ab9775d04..8d7ece39a 100644 --- a/vscode-extension/src/unityConfig.ts +++ b/vscode-extension/src/unityConfig.ts @@ -2,13 +2,17 @@ import * as path from 'node:path'; import { promises as fs } from 'node:fs'; import { pathExists, toErrorMessage } from './utils'; +const CLOUD_SERVER_URL = 'https://ai-game.dev/mcp'; + export interface UnityMcpProjectConfig { exists: boolean; + ready: boolean; host?: string; token?: string; authOption?: 'none' | 'required'; transport?: 'streamableHttp' | 'stdio'; keepConnected?: boolean; + connectionMode: 'custom' | 'cloud'; warnings: string[]; } @@ -24,6 +28,8 @@ export async function readUnityMcpProjectConfig( if (!(await pathExists(configPath))) { return { exists: false, + ready: false, + connectionMode: 'custom', warnings: [], }; } @@ -31,21 +37,40 @@ export async function readUnityMcpProjectConfig( try { const raw = await fs.readFile(configPath, 'utf8'); const parsed = JSON.parse(raw) as Record; + const parsedConnectionMode = parseConnectionMode(parsed['connectionMode']); + const connectionMode = parsedConnectionMode ?? 'custom'; + const authOption = parseAuthOption(parsed['authOption']); + const transport = parseTransport(parsed['transportMethod']); + const host = resolveHost(connectionMode, parsed); + const token = resolveToken(connectionMode, parsed); + const warnings = buildConfigWarnings({ + hasConnectionModeField: 'connectionMode' in parsed, + connectionModeIsValid: parsedConnectionMode !== undefined, + connectionMode, + host, + token, + authOption, + transport, + }); return { exists: true, - host: typeof parsed['host'] === 'string' ? parsed['host'] : undefined, - token: typeof parsed['token'] === 'string' ? parsed['token'] : undefined, - authOption: parseAuthOption(parsed['authOption']), - transport: parseTransport(parsed['transportMethod']), + ready: warnings.length === 0, + host, + token, + authOption, + transport, keepConnected: typeof parsed['keepConnected'] === 'boolean' ? parsed['keepConnected'] : undefined, - warnings: [], + connectionMode, + warnings, }; } catch (error) { return { exists: true, + ready: false, + connectionMode: 'custom', warnings: [ `Could not parse UserSettings/AI-Game-Developer-Config.json: ${toErrorMessage(error)}`, ], @@ -60,3 +85,90 @@ function parseAuthOption(value: unknown): 'none' | 'required' | undefined { function parseTransport(value: unknown): 'streamableHttp' | 'stdio' | undefined { return value === 'stdio' || value === 'streamableHttp' ? value : undefined; } + +function parseConnectionMode(value: unknown): 'custom' | 'cloud' | undefined { + if (value === undefined) { + return undefined; + } + + if (value === 'Cloud' || value === 1) { + return 'cloud'; + } + + if (value === 'Custom' || value === 0) { + return 'custom'; + } + + return undefined; +} + +function resolveHost( + connectionMode: 'custom' | 'cloud', + parsed: Record, +): string | undefined { + if (connectionMode === 'cloud') { + return CLOUD_SERVER_URL; + } + + return readNonEmptyString(parsed['host']); +} + +function resolveToken( + connectionMode: 'custom' | 'cloud', + parsed: Record, +): string | undefined { + const tokenKey = connectionMode === 'cloud' ? 'cloudToken' : 'token'; + return readNonEmptyString(parsed[tokenKey]); +} + +function buildConfigWarnings(input: { + hasConnectionModeField: boolean; + connectionModeIsValid: boolean; + connectionMode: 'custom' | 'cloud'; + host?: string; + token?: string; + authOption?: 'none' | 'required'; + transport?: 'streamableHttp' | 'stdio'; +}): string[] { + const warnings: string[] = []; + + if (input.hasConnectionModeField && !input.connectionModeIsValid) { + warnings.push( + 'UserSettings/AI-Game-Developer-Config.json contains an unsupported connectionMode. Expected "Custom", "Cloud", 0, or 1.', + ); + } + + if (input.authOption === undefined) { + warnings.push( + 'UserSettings/AI-Game-Developer-Config.json is missing a supported authOption. Expected "none" or "required".', + ); + } + + if (input.transport === undefined) { + warnings.push( + 'UserSettings/AI-Game-Developer-Config.json is missing a supported transportMethod. Expected "streamableHttp" or "stdio".', + ); + } + + if (input.host === undefined) { + warnings.push( + input.connectionMode === 'cloud' + ? 'UserSettings/AI-Game-Developer-Config.json is missing a cloud connection URL.' + : 'UserSettings/AI-Game-Developer-Config.json is missing a host URL.', + ); + } + + if (input.authOption === 'required' && input.token === undefined) { + warnings.push( + input.connectionMode === 'cloud' + ? 'UserSettings/AI-Game-Developer-Config.json requires a cloudToken for authenticated MCP launch.' + : 'UserSettings/AI-Game-Developer-Config.json requires a token for authenticated MCP launch.', + ); + } + + return warnings; +} + +function readNonEmptyString(value: unknown): string | undefined { + return typeof value === 'string' && value.trim().length > 0 ? value : undefined; +} diff --git a/vscode-extension/tsconfig.json b/vscode-extension/tsconfig.json index fff3a1dbf..f88fd80f5 100644 --- a/vscode-extension/tsconfig.json +++ b/vscode-extension/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2022", - "module": "commonjs", + "module": "Node16", "lib": [ "ES2022" ], @@ -9,7 +9,7 @@ "rootDir": "src", "strict": true, "esModuleInterop": true, - "moduleResolution": "node", + "moduleResolution": "node16", "skipLibCheck": true, "sourceMap": true, "types": [