diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb65c26..acfd7da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,24 +41,25 @@ jobs: token: ${{ secrets.RELEASE_PLEASE_PAT }} # The following steps only run if a release was created - - name: Setup Node.js + - name: Setup Bun if: steps.release.outputs.release_created - uses: actions/setup-node@v4 + uses: oven-sh/setup-bun@v2 with: - node-version: '20' - cache: 'npm' + bun-version: latest - - name: Install Dependencies (Root) + - name: Setup Node.js (for vsce) if: steps.release.outputs.release_created - run: npm ci --no-fund --no-audit + uses: actions/setup-node@v4 + with: + node-version: '20' - - name: Install Dependencies (Webview) + - name: Install Dependencies if: steps.release.outputs.release_created - run: npm ci --no-fund --no-audit --prefix webview-ui + run: bun install --frozen-lockfile - - name: Build Extension and Webview + - name: Build Extension if: steps.release.outputs.release_created - run: npm run compile + run: bun run build - name: Package Extension if: steps.release.outputs.release_created @@ -101,29 +102,19 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: - node-version: '20' - cache: 'npm' + bun-version: latest - name: Install Dependencies - run: | - npm ci --no-fund --no-audit - npm ci --no-fund --no-audit --prefix webview-ui - - - name: Lint - run: | - npm run lint + run: bun install --frozen-lockfile - - name: Install Xvfb - run: | - sudo apt-get update - sudo apt-get install -y xvfb + - name: Lint & Format Check + run: bun run ci - - name: Run Tests with Xvfb - run: | - xvfb-run -a sh -c "npm run test:all && npm run test:package" + - name: Run Tests + run: bun test - - name: Compile extension and webview - run: npm run compile # This includes build:webview + - name: Build + run: bun run build diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index acf5d05..a12a5cd 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -12,33 +12,21 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - # Checks out the code from the PR branch uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: - node-version: '20' - cache: 'npm' + bun-version: latest - name: Install Dependencies - run: | - npm ci --no-fund --no-audit - npm ci --no-fund --no-audit --prefix webview-ui - - - name: Lint - run: | - npm run lint - - - name: Install Xvfb - run: | - sudo apt-get update - sudo apt-get install -y xvfb - - - name: Run Tests with Xvfb - run: | - # Run standard tests and the package activation test - xvfb-run -a sh -c "npm run test:all && npm run test:package" - - - name: Compile extension and webview - run: npm run compile # This includes build:webview + run: bun install --frozen-lockfile + + - name: Lint & Format Check + run: bun run ci + + - name: Run Tests + run: bun test + + - name: Build + run: bun run build diff --git a/.gitignore b/.gitignore index bd857b0..991508c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,22 +11,12 @@ out/ dist/ *.vsix .vscode-test/ -*.js -*.js.map - -# Exclude these specific files from being ignored -!webview-ui/postcss.config.js -!webview-ui/tailwind.config.js # Binary executables bin/ -bin/goosed +bin/goose bin/*.exe -# Webview build assets -webview-ui/dist/ -webview-ui/node_modules/ - # TypeScript *.tsbuildinfo @@ -52,3 +42,12 @@ Thumbs.db debug*.log coverage/ eslint-report.* + +# Bun lockfile (if using bun) +bun.lockb + +# https://rp1.run knowledge files +!.rp1/ +.rp1/* +!.rp1/context/ +!.rp1/context/** diff --git a/.husky/commit-msg b/.husky/commit-msg index 0a4b97d..343c9ac 100644 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1 +1 @@ -npx --no -- commitlint --edit $1 +bunx --bun commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit index 31ce215..a407424 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,2 @@ -npm run compile +bun run build +bun run ci diff --git a/.rp1/context/architecture.md b/.rp1/context/architecture.md new file mode 100644 index 0000000..657ee1a --- /dev/null +++ b/.rp1/context/architecture.md @@ -0,0 +1,187 @@ +# System Architecture + +**Project**: VS Code Goose +**Architecture Pattern**: Bridge/Adapter with Message-Driven Communication +**Last Updated**: 2025-12-21 + +## High-Level Architecture + +```mermaid +graph TB + subgraph VSCode["VS Code"] + UI["VS Code UI
(Commands, Activity Bar)"] + ExtHost["Extension Host
(Node.js)"] + Webview["Chat Webview
(React/iframe)"] + end + + subgraph Extension["Extension Host Components"] + ExtMain["extension.ts
(Main Entry)"] + SubMgr["SubprocessManager
(Process Lifecycle)"] + JsonRpc["JsonRpcClient
(ACP Protocol)"] + SessMgr["SessionManager
(Session State)"] + WebProv["WebviewProvider
(UI Bridge)"] + VC["VersionChecker"] + FSS["FileSearchService"] + end + + subgraph External["External"] + goose["goose Process
(AI Daemon)"] + GS["globalState"] + end + + UI -->|commands| ExtMain + ExtMain --> SubMgr + ExtMain --> SessMgr + ExtMain --> WebProv + SubMgr -->|spawn/stdin/stdout| goose + SubMgr --> JsonRpc + JsonRpc -->|JSON-RPC 2.0| goose + goose -->|stream notifications| JsonRpc + WebProv <-->|postMessage| Webview + SessMgr --> JsonRpc + SessMgr -->|persist| GS + VC -->|goose --version| goose + FSS -->|workspace.findFiles| UI +``` + +## Architectural Patterns + +### Bridge/Adapter Architecture + +The extension is deliberately minimal - a thin UI layer connecting VS Code webview to Goose backend via Agent Communication Protocol (ACP). No business logic in the extension itself. + +### Layered Architecture + +Four distinct layers with unidirectional dependencies: + +1. **Webview** (presentation) - React components +2. **Extension** (orchestration) - VS Code integration +3. **ACP Client** (protocol) - JSON-RPC handling +4. **Goose subprocess** - External process communication + +### Message-Driven Communication + +All communication between webview and extension uses a strongly-typed message passing system with 24 message types, factory functions, and type guards. + +### Gated Activation + +Multi-stage activation gate: binary discovery → version validation → subprocess spawn. Each stage can block activation with appropriate user messaging. + +## Component Architecture + +### Extension Host Layer + +**Location**: `src/extension/` +**Components**: + +- `extension.ts` - Orchestrates activation, ACP session init, wires components +- `sessionManager.ts` - Session lifecycle, history replay, ACP coordination +- `webviewProvider.ts` - Webview lifecycle, message queue, ready sync +- `subprocessManager.ts` - Spawns goose binary, manages lifecycle events +- `jsonRpcClient.ts` - JSON-RPC 2.0 client with ndjson framing +- `versionChecker.ts` - Binary version validation (>= 1.16.0) +- `fileSearchService.ts` - Workspace file discovery for @ picker +- `commands.ts` - VS Code command registration (showLogs, restart, sendSelectionToChat) + +### Webview Layer + +**Location**: `src/webview/` +**Components**: + +- `App.tsx` - Root component with status, session and chat state +- `bridge.ts` - postMessage abstraction for extension communication +- `ChatView.tsx` - Chat container with keyboard navigation +- `InputArea.tsx` - Input with chips, file picker integration +- `useChat.ts` - Reducer-based chat state management +- `useContextChips.ts` - Chip state management +- `useFilePicker.ts` - @ mention detection and search + +### Shared Layer + +**Location**: `src/shared/` +**Components**: + +- `messages.ts` - 24 WebviewMessage types, payloads, factories, guards +- `types.ts` - ProcessStatus, ChatMessage, MessageContext +- `errors.ts` - GooseError discriminated union, factory functions +- `contextTypes.ts` - ContextChip, FileSearchResult +- `fileReferenceParser.ts` - Parse file references from markdown + +## Key Data Flows + +### User Chat Message Flow + +```mermaid +sequenceDiagram + participant User + participant Webview + participant Extension + participant JsonRpc + participant goose + + User->>Webview: Type message + Enter + Webview->>Extension: SEND_MESSAGE + Extension->>Extension: Build ACP content blocks + Extension->>JsonRpc: session/prompt request + JsonRpc->>goose: JSON-RPC via stdin + + loop Streaming Response + goose-->>JsonRpc: session/update notification + JsonRpc-->>Extension: onNotification callback + Extension-->>Webview: STREAM_TOKEN + Webview-->>User: Render markdown chunk + end + + Extension-->>Webview: GENERATION_COMPLETE +``` + +### Send Selection to Goose (Cmd+Shift+G) + +1. User selects code and presses Cmd+Shift+G +2. `registerContextCommands` handler triggered +3. `goose.chatView.focus` reveals panel +4. `waitForReady()` ensures webview initialized +5. `createAddContextChipMessage()` sent with file/range +6. Webview displays chip and awaits user prompt + +### File Search (@ Picker) + +1. User types @ in chat input +2. `detectAtTrigger()` scans backwards for @ at word boundary +3. Webview sends FILE_SEARCH message with query +4. `fileSearchService.search()` uses `vscode.workspace.findFiles()` +5. Results sorted by recentScore +6. SEARCH_RESULTS message sent back +7. FilePicker dropdown displayed + +### Version-Gated Activation + +1. `discoverBinary()` locates goose binary +2. `checkVersion()` spawns `goose --version` +3. `meetsMinimumVersion()` validates >= 1.16.0 +4. If version fails: `updateVersionStatus()` blocks UI +5. If version passes: spawn subprocess + +## Integration Points + +### Goose ACP Subprocess + +- **Protocol**: JSON-RPC 2.0 over stdin/stdout (ndjson framing) +- **Methods**: `initialize`, `session/new`, `session/load`, `session/prompt` +- **Notifications**: `session/cancel`, `session/update` +- **Version Requirement**: >= 1.16.0 + +### VS Code APIs + +- **Workspace**: `findFiles()` for @ picker +- **Commands**: `goose.showLogs`, `goose.restart`, `goose.sendSelectionToChat` +- **Keybindings**: Cmd+Shift+G for sendSelectionToChat +- **Context Menus**: editor/context menu integration + +## Deployment + +- **Distribution**: VS Code Marketplace via VSIX +- **Build**: Bun bundler, Webpack, Tailwind CSS v4 +- **Linting**: Biome for formatting and linting +- **Webview Options**: `retainContextWhenHidden: true` +- **Version Gate**: Checks goose >= 1.16.0 before subprocess spawn diff --git a/.rp1/context/concept_map.md b/.rp1/context/concept_map.md new file mode 100644 index 0000000..cbaeeed --- /dev/null +++ b/.rp1/context/concept_map.md @@ -0,0 +1,154 @@ +# Domain Concepts & Terminology + +**Project**: VS Code Goose +**Domain**: AI Agent Communication, VS Code Extension Development, Session Management + +## Core Business Concepts + +### ProcessStatus +**Definition**: State machine enum for goose subprocess lifecycle: STOPPED → STARTING → RUNNING → ERROR. Tracks the current operational state of the external goose binary process. +**Implementation**: `src/shared/types.ts` +**Values**: +- `STOPPED`: Process not running +- `STARTING`: Process spawn initiated +- `RUNNING`: Process active and communicating +- `ERROR`: Process crashed or failed to start + +### ChatMessage +**Definition**: Core data structure representing a single message in the chat conversation. Contains id, role (user/assistant/error), content, timestamp, status, and optional context attachments. +**Implementation**: `src/shared/types.ts` +**Key Properties**: +- `id`: Unique identifier for message correlation +- `role`: MessageRole enum (USER, ASSISTANT, ERROR) +- `content`: Text content of the message +- `timestamp`: When the message was created (optional for history messages) +- `status`: MessageStatus tracking lifecycle state +- `originalContent`: For error messages, stores original message for retry +- `context`: Optional array of MessageContext for attached file references + +**Business Rules**: +- User messages are always status=COMPLETE +- Assistant messages transition: PENDING → STREAMING → COMPLETE/CANCELLED/ERROR +- Error messages may have originalContent to enable retry functionality + +### ContextChip +**Definition**: UI element representing a file or code selection reference attached to a chat message. Displayed as removable pills in the input area. +**Implementation**: `src/shared/contextTypes.ts`, `src/webview/hooks/useContextChips.ts` +**Key Properties**: +- `id`: Unique identifier for the chip +- `filePath`: Absolute path to the referenced file +- `fileName`: Display name extracted from path +- `languageId`: VS Code language identifier for syntax highlighting +- `lineRange`: Optional line range (startLine, endLine) for code selections + +**Business Rules**: +- Chips are displayed as removable pills in the input area +- Duplicate detection prevents same file/range being added twice +- Keyboard navigation (arrow keys, backspace) for accessibility + +### MessageContext +**Definition**: Attached file reference within a ChatMessage. Contains filePath, fileName, optional line range, and optional content for history replay. +**Implementation**: `src/shared/types.ts` +**Relationships**: +- Converts from ContextChip when message is sent +- Contains `content` field for history messages (from ACP embedded resource) +- Absent content for live messages (extension reads on demand) + +### FileSearchResult +**Definition**: Search result from @ file picker. Used for selecting files to attach as context. +**Implementation**: `src/shared/contextTypes.ts` +**Key Properties**: +- `path`: Absolute file path +- `fileName`: File name for display +- `relativePath`: Path relative to workspace root +- `languageId`: Language identifier for icon display +- `recentScore`: Timestamp-based ranking for recently accessed files + +### ParsedFileReference +**Definition**: Extracted file reference from markdown content sent by Goose. Used for rendering file content blocks as collapsible cards. +**Implementation**: `src/shared/fileReferenceParser.ts` +**Key Properties**: +- `filePath`: Extracted absolute file path +- `fileName`: File name from path +- `content`: Optional file content from code block +- `language`: Language hint from code fence +- `lineRange`: Optional line range for selections + +### SessionEntry +**Definition**: Stored session metadata containing sessionId, title, cwd (working directory), and createdAt timestamp. Represents a persisted conversation session that can be resumed. +**Implementation**: `src/shared/sessionTypes.ts` + +### AgentCapabilities +**Definition**: Describes ACP agent capabilities received from initialize response. Tracks loadSession support and promptCapabilities (image, audio, embeddedContext). +**Implementation**: `src/shared/sessionTypes.ts` + +### GooseError +**Definition**: Discriminated union of all possible domain errors with `_tag` field for exhaustive type narrowing. +**Implementation**: `src/shared/errors.ts` +**Variants**: +- `BinaryNotFoundError`: Goose binary not found in PATH or configured location +- `SubprocessSpawnError`: Failed to spawn subprocess +- `SubprocessCrashError`: Process exited unexpectedly +- `JsonRpcParseError`: Invalid JSON-RPC response +- `JsonRpcTimeoutError`: Request exceeded timeout +- `JsonRpcError`: Protocol-level error response +- `VersionMismatchError`: Installed version below minimum (1.16.0) + +## Technical Concepts + +### ACP Content Blocks +**Purpose**: Message content types in Agent Communication Protocol for file context +**Implementation**: `src/extension/sessionManager.ts` +**Types**: +- `text`: Plain text content +- `resource_link`: File reference by URI (lightweight, Goose reads file) +- `resource`: Embedded resource with content (for history replay) + +### WebviewMessage Protocol +**Purpose**: Typed message passing between extension host and React webview +**Implementation**: `src/shared/messages.ts` +**Message Types** (24 total): +- Core: WEBVIEW_READY, STATUS_UPDATE, GET_STATUS, ERROR +- Chat: SEND_MESSAGE, STREAM_TOKEN, GENERATION_COMPLETE, STOP_GENERATION, GENERATION_CANCELLED +- Session: CREATE_SESSION, SESSION_CREATED, GET_SESSIONS, SESSIONS_LIST, SELECT_SESSION, SESSION_LOADED +- History: CHAT_HISTORY, HISTORY_MESSAGE, HISTORY_COMPLETE +- Context: ADD_CONTEXT_CHIP, FILE_SEARCH, SEARCH_RESULTS, FOCUS_CHAT_INPUT +- Version: VERSION_STATUS +- Links: OPEN_EXTERNAL_LINK + +### TaskEither Pattern +**Purpose**: fp-ts type for async operations that can fail with typed errors +**Usage**: All subprocess and session operations return TaskEither for composable error handling +**Pattern**: +```typescript +pipe( + operation(), + TE.map(result => transform(result)), + TE.mapLeft(error => handleError(error)) +) +``` + +## Terminology Glossary + +### Business Terms +- **Goose**: The external AI agent binary that provides coding assistance +- **ACP**: Agent Communication Protocol - JSON-RPC interface for goose subprocess +- **Session**: A persistent conversation context with sessionId that can be resumed +- **Context Chip**: Visual pill representing attached file or code selection in chat input +- **@ Mention**: Typing @ at word boundary to activate file picker autocomplete +- **File Picker**: Dropdown for searching and selecting workspace files +- **Streaming Token**: Incremental text chunk from Goose during response generation +- **History Replay**: Loading and displaying past session messages when switching sessions + +### Technical Terms +- **TaskEither**: fp-ts type for async operations that can fail with typed error +- **Discriminated Union**: TypeScript pattern using `_tag` or `type` field for type narrowing +- **Resource Link**: ACP content type for file references without embedded content +- **Embedded Context**: ACP capability for sending file content directly in messages +- **ndjson**: Newline-delimited JSON framing for stdin/stdout communication +- **Recent Score**: Timestamp ranking for file search results prioritization + +## Cross-References +- **Architecture Patterns**: See [architecture.md#patterns] +- **Module Structure**: See [modules.md] +- **Implementation Patterns**: See [patterns.md] diff --git a/.rp1/context/index.md b/.rp1/context/index.md new file mode 100644 index 0000000..f42684a --- /dev/null +++ b/.rp1/context/index.md @@ -0,0 +1,89 @@ +# VS Code Goose - Knowledge Base + +**Type**: Single Project +**Languages**: TypeScript, TSX (React) +**Version**: 0.1.0 +**Updated**: 2025-12-21 + +## Project Summary + +VS Code extension providing a thin UI bridge to Goose AI agent via Agent Communication Protocol (ACP). Enables chat-based coding assistance with context attachment, file search, and session management directly within VS Code. + +## Quick Reference + +| Aspect | Value | +|--------|-------| +| Entry Point | `src/extension/extension.ts` | +| Key Pattern | Bridge/Adapter with Message-Driven Communication | +| Tech Stack | TypeScript, React 19, Tailwind CSS 4, fp-ts, Bun | + +## KB File Manifest + +**Progressive Loading**: Load files on-demand based on your task. + +| File | Lines | Load For | +|------|-------|----------| +| architecture.md | ~137 | System design, component relationships, data flows | +| modules.md | ~160 | Component breakdown, module responsibilities | +| patterns.md | ~70 | Code conventions, implementation patterns | +| concept_map.md | ~150 | Domain terminology, business concepts | + +## Task-Based Loading + +| Task | Files to Load | +|------|---------------| +| Code review | `patterns.md` | +| Bug investigation | `architecture.md`, `modules.md` | +| Feature implementation | `modules.md`, `patterns.md` | +| Context chip work | `concept_map.md`, `modules.md` | +| Strategic analysis | ALL files | + +## How to Load + +``` +Read: .rp1/context/{filename} +``` + +## Project Structure + +``` +src/ +├── extension/ # VS Code extension host (Node.js) +│ ├── extension.ts # Main entry, activation orchestration +│ ├── subprocessManager.ts # Goose process lifecycle +│ ├── jsonRpcClient.ts # ACP JSON-RPC communication +│ ├── sessionManager.ts # Session lifecycle, ACP coordination +│ ├── versionChecker.ts # Binary version validation (>= 1.16.0) +│ ├── fileSearchService.ts # @ file picker search +│ ├── commands.ts # Command registration (Cmd+Shift+G) +│ └── webviewProvider.ts # Webview lifecycle, ready sync +├── webview/ # React chat UI (sandboxed iframe) +│ ├── App.tsx # Root with status, session, chat +│ ├── bridge.ts # postMessage abstraction +│ ├── hooks/ # useChat, useContextChips, useFilePicker +│ ├── components/ +│ │ ├── chat/ # ChatView, InputArea, ChipStack +│ │ ├── picker/ # FilePicker dropdown +│ │ └── session/ # SessionHeader, SessionList +└── shared/ # Shared types between extension/webview + ├── messages.ts # 24 WebviewMessage types + ├── types.ts # ProcessStatus, ChatMessage + ├── errors.ts # GooseError discriminated union + ├── contextTypes.ts # ContextChip, FileSearchResult + └── fileReferenceParser.ts # Parse file refs from markdown +``` + +## Key Features + +- **Context Chips**: Attach files/selections via @ picker or Cmd+Shift+G +- **Version Gating**: Validates goose >= 1.16.0 before activation +- **Session Management**: Persistent sessions with history replay +- **Streaming Responses**: Token-by-token AI response display +- **fp-ts Error Handling**: TaskEither for typed async errors + +## Navigation + +- **[architecture.md](architecture.md)**: System design and diagrams +- **[modules.md](modules.md)**: Component breakdown +- **[patterns.md](patterns.md)**: Code conventions +- **[concept_map.md](concept_map.md)**: Domain terminology diff --git a/.rp1/context/modules.md b/.rp1/context/modules.md new file mode 100644 index 0000000..e48a22a --- /dev/null +++ b/.rp1/context/modules.md @@ -0,0 +1,179 @@ +# Module & Component Breakdown + +**Project**: VS Code Goose +**Analysis Date**: 2025-12-21 +**Modules Analyzed**: 7 module groups + +## Core Modules + +### Extension Module (`src/extension/`) +**Purpose**: VS Code extension host - manages lifecycle, subprocess, ACP session, version checking, file search +**Files**: 12 | **Lines**: ~2,100 + +**Key Components**: +| Component | File | Purpose | +|-----------|------|---------| +| Extension Entry | `extension.ts` | Orchestrates activation, ACP session init, wires components | +| SubprocessManager | `subprocessManager.ts` | Spawns goose binary, manages lifecycle events | +| JsonRpcClient | `jsonRpcClient.ts` | JSON-RPC 2.0 client with ndjson framing | +| SessionManager | `sessionManager.ts` | Session lifecycle, history replay, ACP coordination | +| WebviewProvider | `webviewProvider.ts` | Webview lifecycle, message queue, ready sync | +| Commands | `commands.ts` | Command registration (showLogs, restart, sendSelectionToChat) | +| VersionChecker | `versionChecker.ts` | Binary version validation (>= 1.16.0) | +| FileSearchService | `fileSearchService.ts` | Workspace file search for @ picker | +| BinaryDiscovery | `binaryDiscovery.ts` | Cross-platform goose binary discovery | +| SessionStorage | `sessionStorage.ts` | Persists session metadata to globalState | +| Config | `config.ts` | VS Code configuration reader | +| Logger | `logger.ts` | Source-tagged logger using OutputChannel | + +### Shared Module (`src/shared/`) +**Purpose**: Shared types and utilities between extension and webview +**Files**: 7 | **Lines**: ~1,100 + +**Key Components**: +| Component | File | Purpose | +|-----------|------|---------| +| Messages | `messages.ts` | 24 WebviewMessage types, factories, guards (~700 lines) | +| Types | `types.ts` | ProcessStatus, ChatMessage, MessageRole, MessageContext | +| ContextTypes | `contextTypes.ts` | ContextChip, FileSearchResult, LineRange | +| SessionTypes | `sessionTypes.ts` | SessionEntry, AgentCapabilities, groupSessionsByDate | +| Errors | `errors.ts` | GooseError discriminated union, factory functions | +| FileReferenceParser | `fileReferenceParser.ts` | Parse file references from markdown | +| Index | `index.ts` | Re-exports for convenient imports | + +### Webview Module (`src/webview/`) +**Purpose**: React-based chat UI in sandboxed iframe +**Files**: 28 | **Lines**: ~2,500 + +**Sub-modules**: +- `hooks/` - State management hooks (6 files) +- `components/chat/` - Chat UI components (15 files) +- `components/picker/` - File picker dropdown (2 files) +- `components/icons/` - File type icons (2 files) +- `components/session/` - Session management UI (3 files) +- `components/markdown/` - Markdown rendering (3 files) + +## Key Components + +### SubprocessManager +**File**: `src/extension/subprocessManager.ts` +**Purpose**: Manage goose subprocess from spawn to termination +**Responsibilities**: +- Spawn goose binary with ACP protocol +- Handle process lifecycle events (exit, error) +- Provide JsonRpcClient access +- Graceful shutdown with SIGTERM then SIGKILL + +### JsonRpcClient +**File**: `src/extension/jsonRpcClient.ts` +**Purpose**: JSON-RPC 2.0 over stdin/stdout with ndjson framing +**Responsibilities**: +- Send requests with timeout handling +- Route responses and notifications +- Manage pending request lifecycle + +### SessionManager +**File**: `src/extension/sessionManager.ts` +**Purpose**: Coordinate session state between ACP and storage +**Responsibilities**: +- Create new ACP sessions +- Load sessions with history replay +- Track active session and capabilities +- TaskEither-based API for error handling + +### WebviewProvider +**File**: `src/extension/webviewProvider.ts` +**Purpose**: VS Code WebviewViewProvider for chat panel +**Responsibilities**: +- Generate secure HTML with CSP +- Queue messages until webview ready +- Handle ready sync and reconnection +- Status and version status updates + +### useChat Hook +**File**: `src/webview/hooks/useChat.ts` +**Purpose**: Chat message state management +**Responsibilities**: +- Reducer-based state with typed actions +- Handle streaming tokens +- Send messages with context chips +- Persist input draft + +### useContextChips Hook +**File**: `src/webview/hooks/useContextChips.ts` +**Purpose**: Context chip state management +**Responsibilities**: +- Handle ADD_CONTEXT_CHIP messages +- Detect duplicates +- Keyboard navigation support +- Aria-live announcements + +### useFilePicker Hook +**File**: `src/webview/hooks/useFilePicker.ts` +**Purpose**: @ mention file search +**Responsibilities**: +- Detect @ trigger at word boundary +- Debounced search (100ms) +- Keyboard navigation +- Remove @query after selection + +## Module Dependencies + +```mermaid +graph TD + EXT[extension.ts] --> SubMgr[subprocessManager] + EXT --> SessMgr[sessionManager] + EXT --> WebProv[webviewProvider] + EXT --> CMD[commands] + EXT --> VC[versionChecker] + EXT --> FSS[fileSearchService] + + SubMgr --> JsonRpc[jsonRpcClient] + SessMgr --> JsonRpc + SessMgr --> SessStore[sessionStorage] + WebProv --> MSG[shared/messages] + + APP[App.tsx] --> UseChat[useChat] + APP --> UseSess[useSession] + APP --> UseChips[useContextChips] + CV[ChatView] --> IA[InputArea] + IA --> UFP[useFilePicker] + IA --> CS[ChipStack] + IA --> FP[FilePicker] +``` + +## External Dependencies + +| Package | Version | Purpose | +|---------|---------|---------| +| vscode | ^1.95.0 | VS Code extension API | +| fp-ts | ^2.16.0 | Functional programming | +| react | ^19.1.0 | UI component library | +| react-markdown | ^10.1.0 | Markdown rendering | +| react-syntax-highlighter | ^15.6.1 | Code syntax highlighting | +| @tailwindcss/cli | ^4.1.0 | CSS framework | + +## Module Metrics + +| Module | Files | Lines | Components | +|--------|-------|-------|------------| +| extension | 12 | ~2,100 | 12 | +| shared | 7 | ~1,100 | 6 | +| webview | 28 | ~2,500 | 24 | +| webview/hooks | 6 | ~850 | 6 | +| webview/components/chat | 15 | ~950 | 14 | +| webview/components/picker | 2 | ~160 | 2 | + +## Cross-Module Patterns + +### Context Chip Flow +Editor selection → extension/commands → shared/messages → webview/useContextChips → components/ChipStack → sent with message as resource_link + +### File Picker Request-Response +Webview @ trigger → shared/messages → extension/fileSearchService → shared/messages → webview/FilePicker dropdown + +### Version Gating +extension/versionChecker → shared/messages → webview/VersionBlockedView + +### Bridge Communication +Extension and webview communicate via typed postMessage with factories and guards. Messages are queued until webview ready. diff --git a/.rp1/context/patterns.md b/.rp1/context/patterns.md new file mode 100644 index 0000000..9980921 --- /dev/null +++ b/.rp1/context/patterns.md @@ -0,0 +1,88 @@ +# Implementation Patterns + +**Project**: VS Code Goose +**Last Updated**: 2025-12-21 + +## Naming & Organization + +**Files**: camelCase for source (versionChecker.ts, useFilePicker.ts, ChipStack.tsx) +**Functions**: camelCase with verb prefixes: create*, is* for guards, use* for hooks, parse*, handle* +**Imports**: Named imports, absolute from shared/, relative within same domain + +Evidence: src/shared/messages.ts, src/webview/hooks/useFilePicker.ts + +## Type & Data Modeling + +**Data Representation**: TypeScript interfaces with readonly modifiers +**Type Strictness**: Strict typing with explicit return types, mapped types for payload inference +**Immutability**: Pervasive readonly on properties and arrays (readonly ContextChip[]) +**Discriminated Unions**: `_tag` field for errors, `type` field for messages + +Evidence: src/shared/errors.ts:66-74, src/shared/contextTypes.ts:1-28 + +## Error Handling + +**Strategy**: fp-ts Either/TaskEither for typed async errors +**Propagation**: TaskEither returns E.left/E.right, formatError() for user messages +**Common Types**: BinaryNotFoundError, SubprocessSpawnError, JsonRpcError, VersionMismatchError + +Evidence: src/shared/errors.ts, src/extension/versionChecker.ts:98-181 + +## Validation & Boundaries + +**Location**: API boundary via type guard functions, message entry points +**Method**: Generic isWebviewMessage with specializations, regex for content parsing +**Pattern**: Factory + Guard pairs (createStatusUpdateMessage / isStatusUpdateMessage) + +Evidence: src/shared/messages.ts:538-548, src/shared/fileReferenceParser.ts:53-64 + +## Observability + +**Logging**: Custom Logger abstraction with levels: debug, info, warn, error +**Tracing**: Session IDs and message IDs as correlation identifiers +**Metrics**: None detected + +Evidence: src/extension/logger.ts, src/extension/webviewProvider.ts:52,123 + +## Testing Idioms + +**Organization**: Co-located test files (*.test.ts) +**Framework**: Bun test runner +**Coverage**: Unit tests for pure functions (version parsing, file reference parsing) + +Evidence: src/extension/versionChecker.test.ts, src/shared/fileReferenceParser.test.ts + +## I/O & Integration + +**Subprocess**: spawn with timeout cleanup pattern, JSON-RPC over ndjson-framed stdio +**State Persistence**: VS Code Memento API (globalState), webview getState/setState +**Graceful Shutdown**: SIGTERM then SIGKILL after timeout + +Evidence: src/extension/subprocessManager.ts:86-102, src/extension/jsonRpcClient.ts:109-122 + +## Concurrency & Async + +**Async Usage**: TaskEither for async operations, Promise-based waitForReady() +**Patterns**: Message queue with flush on ready, debounced search (100ms) +**Cleanup**: Returned unsubscribe functions for event handlers + +Evidence: src/extension/webviewProvider.ts:147-154, src/webview/hooks/useFilePicker.ts:99-106 + +## UI Patterns + +**State Management**: useReducer for complex UI state with typed action unions +**Keyboard Navigation**: handleKeyDown returns boolean to indicate consumed event +**Focus Management**: Arrow keys for chip navigation, focus restoration on removal +**Accessibility**: aria-live regions for announcements, role=list/listitem, sr-only class +**@ Trigger Detection**: Scan backwards from cursor for @ at word boundary + +Evidence: src/webview/hooks/useChat.ts:25-36, src/webview/components/chat/ChipStack.tsx:34-99 + +## Communication Patterns + +**Webview-Extension**: postMessage/onMessage with typed discriminated union messages +**Ready Sync**: Promise-based waitForReady() with callback accumulation +**Factory-Guards**: Paired createXMessage() factory and isXMessage() type guard +**State Resend**: Re-send lastStatus/lastVersionStatus on webview reconnect + +Evidence: src/webview/bridge.ts:38-55, src/extension/webviewProvider.ts:105-120 diff --git a/.rp1/context/state.json b/.rp1/context/state.json new file mode 100644 index 0000000..e4816e7 --- /dev/null +++ b/.rp1/context/state.json @@ -0,0 +1,21 @@ +{ + "strategy": "parallel-map-reduce", + "repo_type": "single-project", + "current_project_path": ".", + "monorepo_projects": [], + "generated_at": "2025-12-21T00:00:00.000Z", + "git_commit": "d95458d99cfb3ee2201274631da5f7197e94367f", + "files_analyzed": 69, + "languages": ["TypeScript", "TSX", "CSS"], + "frameworks": ["React 19", "VS Code Extension API", "Tailwind CSS 4", "fp-ts", "react-markdown"], + "metrics": { + "modules": 7, + "components": 42, + "concepts": 24 + }, + "incremental_update": { + "previous_commit": "3c653b9ef95f46e299372a514aa60e0e40b1ad27", + "files_changed": 76, + "mode": "FULL" + } +} diff --git a/.vscode-test.mjs b/.vscode-test.mjs deleted file mode 100644 index d4d7087..0000000 --- a/.vscode-test.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import { defineConfig } from '@vscode/test-cli'; - -export default defineConfig({ - files: 'out/test/**/*.test.js', -}); diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 186459d..dde8236 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,5 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dbaeumer.vscode-eslint", - "ms-vscode.extension-test-runner" - ] + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": ["dbaeumer.vscode-eslint", "ms-vscode.extension-test-runner"] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 8880465..cd94ff9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,21 +1,22 @@ -// A launch configuration that compiles the extension and then opens it inside a new window -// Use IntelliSense to learn about possible attributes. -// Hover to view descriptions of existing attributes. -// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "preLaunchTask": "bun: build", + "sourceMaps": true + }, + { + "name": "Run Extension (No Build)", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "sourceMaps": true + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index afdab66..ffeaf91 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,11 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false // set this to true to hide the "out" folder with the compiled JS files - }, - "search.exclude": { - "out": true // set this to false to include "out" folder in search results - }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3b17e53..745a685 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,20 +1,31 @@ -// See https://go.microsoft.com/fwlink/?LinkId=733558 -// for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "bun run build", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [], + "label": "bun: build", + "detail": "Build extension and webview" + }, + { + "type": "shell", + "command": "bun run dev", + "isBackground": true, + "problemMatcher": [], + "label": "bun: dev", + "detail": "Watch mode for extension development" + }, + { + "type": "shell", + "command": "bun run lint", + "problemMatcher": ["$eslint-stylish"], + "label": "bun: lint", + "detail": "Run ESLint" + } + ] } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fc9a5f0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# Vscode Extension for Goose + +The goal of this project is to ensure a thin UI bridge between Goose and VS Code via ACP - nothing else. + +- We want to be very deliberate when we add new UI features. +- No bloat, absolutely. +- Try and stick to Goose desktop aesthetics as much as possible. +- When choosing libraries to include, choose well supported yet the cleanest and leanest option +- Always look up the latest stable version when installing libraries +- Prefer functional programming +- Prefer using bun tooling as much possible + +## Additional Context + +1. More Context is available here: ./.rp1/context/index.md +2. Progressively load references available in the above file when more context is needed. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..285e0f5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@./AGENTS.md diff --git a/README.md b/README.md index 6002437..181c54d 100644 --- a/README.md +++ b/README.md @@ -1,119 +1,97 @@ -# Goose - VS Code Extension +# Goose for VS Code -[Goose](https://block.github.io/goose/) is an open-source, on-device AI agent that runs locally, works with any LLM provider you configure, and can autonomously tackle full-stack engineering tasks from debugging to deployment. By embedding Goose directly inside VS Code, this extension lets you ask questions, refactor code, generate tests, or spin up entire features without ever leaving your editor. Keep your code in your editor, choose the model that suits you, and let Goose handle the heavy lifting so you can stay in flow +Bring [Goose](https://block.github.io/goose/)—the open-source, on-device AI agent—directly into your editor. Chat with Goose, reference your code, and let it handle full-stack engineering tasks without ever leaving VS Code. -![Screenshot](./resources/screenshot.png) +![Screenshot](./resources/screenshot.gif) -## ⚠️ Experimental Features Notice ⚠️ +> **Note:** This extension is under active development. Some features may change as we continue to improve the experience. We appreciate your feedback! -Please be aware that this extension is under active development. Some features, especially those related to how information (like code context) is processed and sent to the AI, are experimental and may undergo significant changes. We appreciate your understanding and feedback as we work to stabilize and improve these functionalities. +## Features -## Current Features +### Chat with Context -* Interactive chat UI -* Access to Goose's AI capabilities directly within VS Code -* Support for coding assistance, explanations, and more -* Unified session switching -* Code referencing with visual chips in the chat UI -* Quick actions for common coding tasks -* Clipboard tools to copy code snippets/responses for easy sharing -* Keyboard shortcuts for improved productivity (cmd+opt+g / ctrl+alt+g by default for sending code to Goose; configurable) +Ask Goose questions about your code with full file context. Select code in your editor and send it to Goose with a single keystroke, or type `@` to search and attach any file from your workspace. -## Coming Soon +### Context Chips -* Smart Auto-fix loop (Let Goose automatically fix it's own mistakes based on VS Code diagnostics) -* Code action suggestions for diagnostics and terminal output -* Diff views for code changes +Attach multiple files or code selections to your messages. Visual chips show exactly what context Goose sees, with support for both entire files and specific line ranges. -## Requirements +### Session Management -> ⚠️ **VS Code 1.95.0 or higher is required** for this extension to function properly. +Pick up where you left off. Your conversations persist across VS Code sessions with full history—browse past chats organized by time and switch between sessions instantly. -> ⚠️ **Goose Desktop must be installed** before using this extension. -👉 Install Goose Desktop from [here](https://block.github.io/goose/) +### Streaming Responses -## Installation +See Goose think in real-time. Responses stream token-by-token with syntax-highlighted code blocks and one-click copy. -There are two ways to install the Goose VS Code Extension: +## Requirements -### Method 1: Install from VS Code Marketplace (recommended) -[Install from Market Place](https://marketplace.visualstudio.com/items?itemName=block.vscode-goose) +- **VS Code 1.95.0+** +- **Goose Desktop 1.16.0+** — [Install Goose](https://block.github.io/goose/) -### Method 2: Install from GitHub Releases +## Installation -1. Go to the [GitHub Releases page](https://github.com/block/vscode-goose/releases) -2. Find the latest release with the tag `vscode-v*` -3. Download the `.vsix` file -4. In VS Code, go to the Extensions view (Ctrl+Shift+X) -5. Click on the "..." menu at the top of the Extensions view -6. Select "Install from VSIX..." -7. Locate and select the downloaded `.vsix` file -8. Restart VS Code if prompted +### From VS Code Marketplace (Recommended) -### Chat Interface +[Install from Marketplace](https://marketplace.visualstudio.com/items?itemName=block.vscode-goose) -The Goose chat interface appears in the sidebar activity bar. Click the Goose icon to open the chat panel. +### From GitHub Releases -### Code References +1. Download the `.vsix` from [Releases](https://github.com/block/vscode-goose/releases) +2. In VS Code: Extensions → `...` menu → Install from VSIX... -You can reference code from your editor in your conversations with Goose: +## Quick Start -1. Select code in your editor (or don't select anything to use the entire file) -2. Right-click and choose "Ask Goose about this code" or use the keyboard shortcut Ctrl+Alt+G (Cmd+Option+G on macOS) -3. The chat input will be automatically focused, allowing you to immediately start typing your question +1. Click the Goose icon in the Activity Bar +2. Start typing your question +3. Use `@` to attach files or Cmd+Shift+G to send selected code -The behavior varies based on how much code is selected: +## Usage -* **No selection:** The entire active file is sent as a reference chip -* **Small selections (< 100 lines):** The selected code is automatically included inline with your message -* **Large selections (≥ 100 lines):** The code is added as a reference chip above the input box +### Send Code to Goose -This adaptive approach provides the best experience for different code sizes. +Select code in your editor and press Cmd+Shift+G (macOS) or Ctrl+Shift+G (Windows/Linux). You can also right-click and choose **Send to Goose**. -### Quick Actions +- **No selection**: Sends the entire file as context +- **Small selection** (<100 lines): Included inline with your message +- **Large selection** (≥100 lines): Added as a context chip -The extension currently provides the following quick action command that can be accessed by right-clicking on selected code: +### Attach Files with @ Mentions -* **Ask Goose about this code** - General question about the selected code +Type `@` in the chat input to search your workspace. Select a file to add it as a context chip—Goose will see the full file contents. -### Keyboard Shortcuts +### Manage Sessions -| Command | Shortcut (Windows/Linux) | Shortcut (macOS) | -| ----------------------------- | ------------------------ | ----------------------- | -| Ask Goose about selected code | Ctrl+Alt+G | Cmd+Option+G | +- **New Chat**: Start a fresh conversation +- **History**: Browse and resume past sessions grouped by Today, Yesterday, and older -## Extension Settings +## Keyboard Shortcuts -This extension contributes the following settings: +| Action | macOS | Windows/Linux | +|--------|-------|---------------| +| Send selection to Goose | Cmd+Shift+G | Ctrl+Shift+G | -* `goose.enable`: enable/disable this extension +## Configuration -## Configuration File Location +| Setting | Description | +|---------|-------------| +| `goose.binaryPath` | Path to Goose binary (auto-detected by default) | +| `goose.logLevel` | Logging level: `error`, `warn`, `info`, `debug` | -Goose's CLI and this extension look for a configuration file containing -`GOOSE_PROVIDER` and `GOOSE_MODEL` values. The default locations are: +Goose reads its provider and model configuration from: -* **Linux/macOS:** `~/.config/goose/config.yaml` -* **Windows:** `%APPDATA%\Block\goose\config\config.yaml` (e.g. `C:\Users\\AppData\Roaming\Block\goose\config\config.yaml`) +- **macOS/Linux**: `~/.config/goose/config.yaml` +- **Windows**: `%APPDATA%\Block\goose\config\config.yaml` -Ensure this file exists and is populated with valid values before starting the extension. +## Commands ----- +- **Goose: Show Logs** — View extension logs +- **Goose: Restart** — Restart the Goose connection ## Support -For support, bug reports, or feature suggestions, please use [GitHub Issues](https://github.com/block/vscode-goose/issues). - - -## Architecture - -Detailed information about the extension's architecture can be found in [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md). - -## Development - -Information for developers contributing to this extension can be found in [docs/DEVELOPMENT.md](./docs/DEVELOPMENT.md). +Questions or issues? Open an issue on [GitHub](https://github.com/block/vscode-goose/issues). ## License -This extension is licensed under Apache-2.0. -See the [LICENSE](./LICENSE) file for details. +Apache-2.0 — see [LICENSE](./LICENSE) diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..54fc39a --- /dev/null +++ b/biome.json @@ -0,0 +1,337 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.10/schema.json", + "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, + "files": { "includes": ["**", "!!**/dist", "!!**/*.css"] }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 100, + "attributePosition": "auto", + "bracketSameLine": false, + "bracketSpacing": true, + "expand": "auto", + "useEditorconfig": true, + "includes": [ + "**", + "!**/dist/", + "!**/node_modules/", + "!**/.vscode-test/", + "!**/*.vsix", + "!**/package-lock.json", + "!**/bun.lockb" + ] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "complexity": { + "noAdjacentSpacesInRegex": "error", + "noExtraBooleanCast": "error", + "noUselessCatch": "error", + "noUselessEscapeInRegex": "error", + "noUselessTypeConstraint": "error" + }, + "correctness": { + "noConstAssign": "error", + "noConstantCondition": "error", + "noEmptyCharacterClassInRegex": "error", + "noEmptyPattern": "error", + "noGlobalObjectCalls": "error", + "noInvalidBuiltinInstantiation": "error", + "noInvalidConstructorSuper": "error", + "noNonoctalDecimalEscape": "error", + "noPrecisionLoss": "error", + "noSelfAssign": "error", + "noSetterReturn": "error", + "noSwitchDeclarations": "error", + "noUndeclaredVariables": "error", + "noUnreachable": "error", + "noUnreachableSuper": "error", + "noUnsafeFinally": "error", + "noUnsafeOptionalChaining": "error", + "noUnusedLabels": "error", + "noUnusedPrivateClassMembers": "error", + "noUnusedVariables": "error", + "useIsNan": "error", + "useValidForDirection": "error", + "useValidTypeof": "error", + "useYield": "error" + }, + "style": { + "noCommonJs": "error", + "noNamespace": "error", + "useArrayLiterals": "error", + "useAsConstAssertion": "error" + }, + "suspicious": { + "noAsyncPromiseExecutor": "error", + "noCatchAssign": "error", + "noClassAssign": "error", + "noCompareNegZero": "error", + "noConstantBinaryExpressions": "error", + "noControlCharactersInRegex": "error", + "noDebugger": "error", + "noDuplicateCase": "error", + "noDuplicateClassMembers": "error", + "noDuplicateElseIf": "error", + "noDuplicateObjectKeys": "error", + "noDuplicateParameters": "error", + "noEmptyBlockStatements": "error", + "noExplicitAny": "error", + "noExtraNonNullAssertion": "error", + "noFallthroughSwitchClause": "error", + "noFunctionAssign": "error", + "noGlobalAssign": "error", + "noImportAssign": "error", + "noIrregularWhitespace": "error", + "noMisleadingCharacterClass": "error", + "noMisleadingInstantiator": "error", + "noNonNullAssertedOptionalChain": "error", + "noPrototypeBuiltins": "error", + "noRedeclare": "error", + "noShadowRestrictedNames": "error", + "noSparseArray": "error", + "noUnsafeDeclarationMerging": "error", + "noUnsafeNegation": "error", + "noUselessRegexBackrefs": "error", + "noWith": "error", + "useGetterReturn": "error", + "useNamespaceKeyword": "error" + } + }, + "includes": ["**", "!dist/**", "!node_modules/**", "!.vscode-test/**", "!*.js", "!*.mjs"] + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "es5", + "semicolons": "always", + "arrowParentheses": "asNeeded", + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto", + "bracketSpacing": true + }, + "globals": [] + }, + "html": { + "formatter": { + "indentScriptAndStyle": false, + "selfCloseVoidElements": "always" + } + }, + "css": { + "parser": { + "cssModules": true, + "allowWrongLineComments": false + }, + "linter": { + "enabled": false + }, + "formatter": { + "enabled": false + } + }, + "overrides": [ + { + "includes": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], + "linter": { + "rules": { + "complexity": { "noArguments": "error" }, + "correctness": { + "noConstAssign": "off", + "noGlobalObjectCalls": "off", + "noInvalidBuiltinInstantiation": "off", + "noInvalidConstructorSuper": "off", + "noSetterReturn": "off", + "noUndeclaredVariables": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off" + }, + "style": { "useConst": "error" }, + "suspicious": { + "noClassAssign": "off", + "noDuplicateClassMembers": "off", + "noDuplicateObjectKeys": "off", + "noDuplicateParameters": "off", + "noFunctionAssign": "off", + "noImportAssign": "off", + "noRedeclare": "off", + "noUnsafeNegation": "off", + "noVar": "error", + "noWith": "off", + "useGetterReturn": "off" + } + } + } + }, + { + "includes": ["src/extension/**/*.ts", "src/shared/**/*.ts"], + "javascript": { "globals": ["exports"] }, + "linter": { + "rules": { + "correctness": { "noUnusedVariables": "error" }, + "style": { "noNonNullAssertion": "warn" }, + "suspicious": { "noExplicitAny": "warn" } + } + } + }, + { + "includes": ["src/webview/**/*.tsx", "src/webview/**/*.ts"], + "javascript": { + "globals": [ + "onanimationend", + "ongamepadconnected", + "onlostpointercapture", + "onanimationiteration", + "onkeyup", + "onmousedown", + "onanimationstart", + "onslotchange", + "onprogress", + "ontransitionstart", + "onpause", + "onended", + "onpointerover", + "onscrollend", + "onformdata", + "ontransitionrun", + "onanimationcancel", + "ondrag", + "onchange", + "onbeforeinstallprompt", + "onbeforexrselect", + "onmessage", + "ontransitioncancel", + "onpointerdown", + "onabort", + "onpointerout", + "oncuechange", + "ongotpointercapture", + "onscrollsnapchanging", + "onsearch", + "onsubmit", + "onstalled", + "onsuspend", + "onreset", + "onerror", + "onresize", + "onmouseenter", + "ongamepaddisconnected", + "ondragover", + "onbeforetoggle", + "onmouseover", + "onpagehide", + "onmousemove", + "onratechange", + "onmessageerror", + "onwheel", + "ondevicemotion", + "onauxclick", + "ontransitionend", + "onpaste", + "onpageswap", + "ononline", + "ondeviceorientationabsolute", + "onkeydown", + "onclose", + "onselect", + "onpageshow", + "onpointercancel", + "onbeforematch", + "onpointerrawupdate", + "ondragleave", + "onscrollsnapchange", + "onseeked", + "onwaiting", + "onbeforeunload", + "onplaying", + "onvolumechange", + "ondragend", + "onstorage", + "onloadeddata", + "onfocus", + "onoffline", + "onplay", + "onafterprint", + "onclick", + "oncut", + "onmouseout", + "ondblclick", + "oncanplay", + "onloadstart", + "onappinstalled", + "onpointermove", + "ontoggle", + "oncontextmenu", + "onblur", + "oncancel", + "onbeforeprint", + "oncontextrestored", + "onloadedmetadata", + "onpointerup", + "onlanguagechange", + "oncopy", + "onselectstart", + "onscroll", + "onload", + "ondragstart", + "onbeforeinput", + "oncanplaythrough", + "oninput", + "oninvalid", + "ontimeupdate", + "ondurationchange", + "onselectionchange", + "onmouseup", + "location", + "onkeypress", + "onpointerleave", + "oncontextlost", + "ondrop", + "onsecuritypolicyviolation", + "oncontentvisibilityautostatechange", + "ondeviceorientation", + "onseeking", + "onrejectionhandled", + "onunload", + "onmouseleave", + "onhashchange", + "onpointerenter", + "onmousewheel", + "onunhandledrejection", + "ondragenter", + "onpopstate", + "onpagereveal", + "onemptied" + ] + }, + "linter": { + "rules": { + "correctness": { + "noChildrenProp": "error", + "noUnusedVariables": "error", + "useExhaustiveDependencies": "warn", + "useHookAtTopLevel": "error", + "useJsxKeyInIterable": "error" + }, + "security": { "noDangerouslySetInnerHtmlWithChildren": "error" }, + "suspicious": { + "noCommentText": "error", + "noDuplicateJsxProps": "error", + "noExplicitAny": "warn" + } + } + } + } + ], + "assist": { + "enabled": true, + "actions": { "source": { "organizeImports": "on" } } + } +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..fddec10 --- /dev/null +++ b/bun.lock @@ -0,0 +1,1267 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "vscode-goose", + "dependencies": { + "fp-ts": "^2.16.0", + "react-markdown": "^10.1.0", + "react-syntax-highlighter": "^15.6.1", + "remark-gfm": "^4.0.1", + "zod": "^3.23.0", + }, + "devDependencies": { + "@biomejs/biome": "2.3.10", + "@commitlint/cli": "^19.6.0", + "@commitlint/config-conventional": "^19.6.0", + "@tailwindcss/cli": "^4.1.0", + "@types/node": "^20.17.0", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@types/react-syntax-highlighter": "^15.5.13", + "@types/vscode": "^1.95.0", + "@vscode/vsce": "^3.2.0", + "autoprefixer": "^10.4.16", + "husky": "^9.1.0", + "postcss": "^8.4.32", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.8.0", + }, + }, + }, + "packages": { + "@azu/format-text": ["@azu/format-text@1.0.2", "", {}, "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg=="], + + "@azu/style-format": ["@azu/style-format@1.0.1", "", { "dependencies": { "@azu/format-text": "^1.0.1" } }, "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g=="], + + "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], + + "@azure/core-client": ["@azure/core-client@1.10.1", "", { "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" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="], + + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.22.2", "", { "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.0", "tslib": "^2.6.2" } }, "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg=="], + + "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], + + "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], + + "@azure/identity": ["@azure/identity@4.13.0", "", { "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": "^4.2.0", "@azure/msal-node": "^3.5.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-uWC0fssc+hs1TGGVkkghiaFkkS7NkTxfnCH+Hdg+yTehTpMcehpok4PgUKKdyCH+9ldu6FhiHRv84Ntqj1vVcw=="], + + "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], + + "@azure/msal-browser": ["@azure/msal-browser@4.26.2", "", { "dependencies": { "@azure/msal-common": "15.13.2" } }, "sha512-F2U1mEAFsYGC5xzo1KuWc/Sy3CRglU9Ql46cDUx8x/Y3KnAIr1QAq96cIKCk/ZfnVxlvprXWRjNKoEpgLJXLhg=="], + + "@azure/msal-common": ["@azure/msal-common@15.13.2", "", {}, "sha512-cNwUoCk3FF8VQ7Ln/MdcJVIv3sF73/OT86cRH81ECsydh7F4CNfIo2OAx6Cegtg8Yv75x4506wN4q+Emo6erOA=="], + + "@azure/msal-node": ["@azure/msal-node@3.8.3", "", { "dependencies": { "@azure/msal-common": "15.13.2", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-Ul7A4gwmaHzYWj2Z5xBDly/W8JSC1vnKgJ898zPMZr0oSf1ah0tiL15sytjycU/PMhDZAlkWtEL1+MzNMU6uww=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + + "@biomejs/biome": ["@biomejs/biome@2.3.10", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.10", "@biomejs/cli-darwin-x64": "2.3.10", "@biomejs/cli-linux-arm64": "2.3.10", "@biomejs/cli-linux-arm64-musl": "2.3.10", "@biomejs/cli-linux-x64": "2.3.10", "@biomejs/cli-linux-x64-musl": "2.3.10", "@biomejs/cli-win32-arm64": "2.3.10", "@biomejs/cli-win32-x64": "2.3.10" }, "bin": { "biome": "bin/biome" } }, "sha512-/uWSUd1MHX2fjqNLHNL6zLYWBbrJeG412/8H7ESuK8ewoRoMPUgHDebqKrPTx/5n6f17Xzqc9hdg3MEqA5hXnQ=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M6xUjtCVnNGFfK7HMNKa593nb7fwNm43fq1Mt71kpLpb+4mE7odO8W/oWVDyBVO4ackhresy1ZYO7OJcVo/B7w=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vae7+V6t/Avr8tVbFNjnFSTKZogZHFYl7MMH62P/J1kZtr0tyRQ9Fe0onjqjS2Ek9lmNLmZc/VR5uSekh+p1fg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-hhPw2V3/EpHKsileVOFynuWiKRgFEV48cLe0eA+G2wO4SzlwEhLEB9LhlSrVeu2mtSn205W283LkX7Fh48CaxA=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-B9DszIHkuKtOH2IFeeVkQmSMVUjss9KtHaNXquYYWCjH8IstNgXgx5B0aSBQNr6mn4RcKKRQZXn9Zu1rM3O0/A=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-wwAkWD1MR95u+J4LkWP74/vGz+tRrIQvr8kfMMJY8KOQ8+HMVleREOcPYsQX82S7uueco60L58Wc6M1I9WA9Dw=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QTfHZQh62SDFdYc2nfmZFuTm5yYb4eO1zwfB+90YxUumRCR171tS1GoTX5OD0wrv4UsziMPmrePMtkTnNyYG3g=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-o7lYc9n+CfRbHvkjPhm8s9FgbKdYZu5HCcGVMItLjz93EhgJ8AM44W+QckDqLA9MKDNFrR8nPbO4b73VC5kGGQ=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.10", "", { "os": "win32", "cpu": "x64" }, "sha512-pHEFgq7dUEsKnqG9mx9bXihxGI49X+ar+UBrEIj3Wqj3UCZp1rNgV+OoyjFgcXsjCWpuEAF4VJdkZr3TrWdCbQ=="], + + "@commitlint/cli": ["@commitlint/cli@19.8.1", "", { "dependencies": { "@commitlint/format": "^19.8.1", "@commitlint/lint": "^19.8.1", "@commitlint/load": "^19.8.1", "@commitlint/read": "^19.8.1", "@commitlint/types": "^19.8.1", "tinyexec": "^1.0.0", "yargs": "^17.0.0" }, "bin": { "commitlint": "./cli.js" } }, "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA=="], + + "@commitlint/config-conventional": ["@commitlint/config-conventional@19.8.1", "", { "dependencies": { "@commitlint/types": "^19.8.1", "conventional-changelog-conventionalcommits": "^7.0.2" } }, "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ=="], + + "@commitlint/config-validator": ["@commitlint/config-validator@19.8.1", "", { "dependencies": { "@commitlint/types": "^19.8.1", "ajv": "^8.11.0" } }, "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ=="], + + "@commitlint/ensure": ["@commitlint/ensure@19.8.1", "", { "dependencies": { "@commitlint/types": "^19.8.1", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", "lodash.startcase": "^4.4.0", "lodash.upperfirst": "^4.3.1" } }, "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw=="], + + "@commitlint/execute-rule": ["@commitlint/execute-rule@19.8.1", "", {}, "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA=="], + + "@commitlint/format": ["@commitlint/format@19.8.1", "", { "dependencies": { "@commitlint/types": "^19.8.1", "chalk": "^5.3.0" } }, "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw=="], + + "@commitlint/is-ignored": ["@commitlint/is-ignored@19.8.1", "", { "dependencies": { "@commitlint/types": "^19.8.1", "semver": "^7.6.0" } }, "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg=="], + + "@commitlint/lint": ["@commitlint/lint@19.8.1", "", { "dependencies": { "@commitlint/is-ignored": "^19.8.1", "@commitlint/parse": "^19.8.1", "@commitlint/rules": "^19.8.1", "@commitlint/types": "^19.8.1" } }, "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw=="], + + "@commitlint/load": ["@commitlint/load@19.8.1", "", { "dependencies": { "@commitlint/config-validator": "^19.8.1", "@commitlint/execute-rule": "^19.8.1", "@commitlint/resolve-extends": "^19.8.1", "@commitlint/types": "^19.8.1", "chalk": "^5.3.0", "cosmiconfig": "^9.0.0", "cosmiconfig-typescript-loader": "^6.1.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "lodash.uniq": "^4.5.0" } }, "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A=="], + + "@commitlint/message": ["@commitlint/message@19.8.1", "", {}, "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg=="], + + "@commitlint/parse": ["@commitlint/parse@19.8.1", "", { "dependencies": { "@commitlint/types": "^19.8.1", "conventional-changelog-angular": "^7.0.0", "conventional-commits-parser": "^5.0.0" } }, "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw=="], + + "@commitlint/read": ["@commitlint/read@19.8.1", "", { "dependencies": { "@commitlint/top-level": "^19.8.1", "@commitlint/types": "^19.8.1", "git-raw-commits": "^4.0.0", "minimist": "^1.2.8", "tinyexec": "^1.0.0" } }, "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ=="], + + "@commitlint/resolve-extends": ["@commitlint/resolve-extends@19.8.1", "", { "dependencies": { "@commitlint/config-validator": "^19.8.1", "@commitlint/types": "^19.8.1", "global-directory": "^4.0.1", "import-meta-resolve": "^4.0.0", "lodash.mergewith": "^4.6.2", "resolve-from": "^5.0.0" } }, "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg=="], + + "@commitlint/rules": ["@commitlint/rules@19.8.1", "", { "dependencies": { "@commitlint/ensure": "^19.8.1", "@commitlint/message": "^19.8.1", "@commitlint/to-lines": "^19.8.1", "@commitlint/types": "^19.8.1" } }, "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw=="], + + "@commitlint/to-lines": ["@commitlint/to-lines@19.8.1", "", {}, "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg=="], + + "@commitlint/top-level": ["@commitlint/top-level@19.8.1", "", { "dependencies": { "find-up": "^7.0.0" } }, "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw=="], + + "@commitlint/types": ["@commitlint/types@19.8.1", "", { "dependencies": { "@types/conventional-commits-parser": "^5.0.0", "chalk": "^5.3.0" } }, "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw=="], + + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], + + "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], + + "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="], + + "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw=="], + + "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg=="], + + "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ=="], + + "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA=="], + + "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q=="], + + "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w=="], + + "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg=="], + + "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A=="], + + "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg=="], + + "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw=="], + + "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ=="], + + "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="], + + "@secretlint/config-creator": ["@secretlint/config-creator@10.2.2", "", { "dependencies": { "@secretlint/types": "^10.2.2" } }, "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ=="], + + "@secretlint/config-loader": ["@secretlint/config-loader@10.2.2", "", { "dependencies": { "@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" } }, "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ=="], + + "@secretlint/core": ["@secretlint/core@10.2.2", "", { "dependencies": { "@secretlint/profiler": "^10.2.2", "@secretlint/types": "^10.2.2", "debug": "^4.4.1", "structured-source": "^4.0.0" } }, "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw=="], + + "@secretlint/formatter": ["@secretlint/formatter@10.2.2", "", { "dependencies": { "@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" } }, "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA=="], + + "@secretlint/node": ["@secretlint/node@10.2.2", "", { "dependencies": { "@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" } }, "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ=="], + + "@secretlint/profiler": ["@secretlint/profiler@10.2.2", "", {}, "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig=="], + + "@secretlint/resolver": ["@secretlint/resolver@10.2.2", "", {}, "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w=="], + + "@secretlint/secretlint-formatter-sarif": ["@secretlint/secretlint-formatter-sarif@10.2.2", "", { "dependencies": { "node-sarif-builder": "^3.2.0" } }, "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ=="], + + "@secretlint/secretlint-rule-no-dotenv": ["@secretlint/secretlint-rule-no-dotenv@10.2.2", "", { "dependencies": { "@secretlint/types": "^10.2.2" } }, "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg=="], + + "@secretlint/secretlint-rule-preset-recommend": ["@secretlint/secretlint-rule-preset-recommend@10.2.2", "", {}, "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA=="], + + "@secretlint/source-creator": ["@secretlint/source-creator@10.2.2", "", { "dependencies": { "@secretlint/types": "^10.2.2", "istextorbinary": "^9.5.0" } }, "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw=="], + + "@secretlint/types": ["@secretlint/types@10.2.2", "", {}, "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], + + "@tailwindcss/cli": ["@tailwindcss/cli@4.1.17", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "enhanced-resolve": "^5.18.3", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.1.17" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-jUIxcyUNlCC2aNPnyPEWU/L2/ik3pB4fF3auKGXr8AvN3T3OFESVctFKOBoPZQaZJIeUpPn1uCLp0MRxuek8gg=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="], + + "@textlint/ast-node-types": ["@textlint/ast-node-types@15.4.0", "", {}, "sha512-IqY8i7IOGuvy05wZxISB7Me1ZyrvhaQGgx6DavfQjH3cfwpPFdDbDYmMXMuSv2xLS1kDB1kYKBV7fL2Vi16lRA=="], + + "@textlint/linter-formatter": ["@textlint/linter-formatter@15.4.0", "", { "dependencies": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", "@textlint/module-interop": "15.4.0", "@textlint/resolver": "15.4.0", "@textlint/types": "15.4.0", "chalk": "^4.1.2", "debug": "^4.4.3", "js-yaml": "^3.14.1", "lodash": "^4.17.21", "pluralize": "^2.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "table": "^6.9.0", "text-table": "^0.2.0" } }, "sha512-rfqOZmnI1Wwc/Pa4LK+vagvVPmvxf9oRsBRqIOB04DwhucingZyAIJI/TyG18DIDYbP2aFXBZ3oOvyAxHe/8PQ=="], + + "@textlint/module-interop": ["@textlint/module-interop@15.4.0", "", {}, "sha512-uGf+SFIfzOLCbZI0gp+2NLsrkSArsvEWulPP6lJuKp7yRHadmy7Xf/YHORe46qhNyyxc8PiAfiixHJSaHGUrGg=="], + + "@textlint/resolver": ["@textlint/resolver@15.4.0", "", {}, "sha512-Vh/QceKZQHFJFG4GxxIsKM1Xhwv93mbtKHmFE5/ybal1mIKHdqF03Z9Guaqt6Sx/AeNUshq0hkMOEhEyEWnehQ=="], + + "@textlint/types": ["@textlint/types@15.4.0", "", { "dependencies": { "@textlint/ast-node-types": "15.4.0" } }, "sha512-ZMwJgw/xjxJufOD+IB7I2Enl9Si4Hxo04B76RwUZ5cKBKzOPcmd6WvGe2F7jqdgmTdGnfMU+Bo/joQrjPNIWqg=="], + + "@types/conventional-commits-parser": ["@types/conventional-commits-parser@5.0.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g=="], + + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="], + + "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], + + "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@types/react-syntax-highlighter": ["@types/react-syntax-highlighter@15.5.13", "", { "dependencies": { "@types/react": "*" } }, "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA=="], + + "@types/sarif": ["@types/sarif@2.1.7", "", {}, "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@types/vscode": ["@types/vscode@1.106.1", "", {}, "sha512-R/HV8u2h8CAddSbX8cjpdd7B8/GnE4UjgjpuGuHcbp1xV6yh4OeqU4L1pKjlwujCrSFS0MOpwJAIs/NexMB1fQ=="], + + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.2", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "@vscode/vsce": ["@vscode/vsce@3.7.1", "", { "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": "^11.0.0", "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": "^3.0.3", "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": "^2.3.1", "yazl": "^2.2.2" }, "optionalDependencies": { "keytar": "^7.7.0" }, "bin": { "vsce": "vsce" } }, "sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g=="], + + "@vscode/vsce-sign": ["@vscode/vsce-sign@2.0.9", "", { "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" } }, "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g=="], + + "@vscode/vsce-sign-alpine-arm64": ["@vscode/vsce-sign-alpine-arm64@2.0.6", "", { "os": "none", "cpu": "arm64" }, "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q=="], + + "@vscode/vsce-sign-alpine-x64": ["@vscode/vsce-sign-alpine-x64@2.0.6", "", { "os": "none", "cpu": "x64" }, "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w=="], + + "@vscode/vsce-sign-darwin-arm64": ["@vscode/vsce-sign-darwin-arm64@2.0.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ=="], + + "@vscode/vsce-sign-darwin-x64": ["@vscode/vsce-sign-darwin-x64@2.0.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw=="], + + "@vscode/vsce-sign-linux-arm": ["@vscode/vsce-sign-linux-arm@2.0.6", "", { "os": "linux", "cpu": "arm" }, "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA=="], + + "@vscode/vsce-sign-linux-arm64": ["@vscode/vsce-sign-linux-arm64@2.0.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA=="], + + "@vscode/vsce-sign-linux-x64": ["@vscode/vsce-sign-linux-x64@2.0.6", "", { "os": "linux", "cpu": "x64" }, "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA=="], + + "@vscode/vsce-sign-win32-arm64": ["@vscode/vsce-sign-win32-arm64@2.0.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg=="], + + "@vscode/vsce-sign-win32-x64": ["@vscode/vsce-sign-win32-x64@2.0.6", "", { "os": "win32", "cpu": "x64" }, "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ=="], + + "JSONStream": ["JSONStream@1.3.5", "", { "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" }, "bin": { "JSONStream": "./bin.js" } }, "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-ify": ["array-ify@1.0.0", "", {}, "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng=="], + + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "autoprefixer": ["autoprefixer@10.4.22", "", { "dependencies": { "browserslist": "^4.27.0", "caniuse-lite": "^1.0.30001754", "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg=="], + + "azure-devops-node-api": ["azure-devops-node-api@12.5.0", "", { "dependencies": { "tunnel": "0.0.6", "typed-rest-client": "^1.8.4" } }, "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="], + + "binaryextensions": ["binaryextensions@6.11.0", "", { "dependencies": { "editions": "^6.21.0" } }, "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "boundary": ["boundary@2.0.0", "", {}, "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "character-entities": ["character-entities@1.2.4", "", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@1.1.4", "", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="], + + "character-reference-invalid": ["character-reference-invalid@1.1.4", "", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="], + + "cheerio": ["cheerio@1.1.2", "", { "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.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg=="], + + "cheerio-select": ["cheerio-select@2.1.0", "", { "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" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], + + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "cockatiel": ["cockatiel@3.2.1", "", {}, "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "compare-func": ["compare-func@2.0.0", "", { "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], + + "conventional-changelog-conventionalcommits": ["conventional-changelog-conventionalcommits@7.0.2", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w=="], + + "conventional-commits-parser": ["conventional-commits-parser@5.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="], + + "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], + + "cosmiconfig-typescript-loader": ["cosmiconfig-typescript-loader@6.2.0", "", { "dependencies": { "jiti": "^2.6.1" }, "peerDependencies": { "@types/node": "*", "cosmiconfig": ">=9", "typescript": ">=5" } }, "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "dargs": ["dargs@8.1.0", "", {}, "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "editions": ["editions@6.22.0", "", { "dependencies": { "version-range": "^4.15.0" } }, "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.262", "", {}, "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "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" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fault": ["fault@1.0.4", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="], + + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "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" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + + "fp-ts": ["fp-ts@2.16.11", "", {}, "sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w=="], + + "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "git-raw-commits": ["git-raw-commits@4.0.0", "", { "dependencies": { "dargs": "^8.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.mjs" } }, "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "global-directory": ["global-directory@4.0.1", "", { "dependencies": { "ini": "4.1.1" } }, "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q=="], + + "globby": ["globby@14.1.0", "", { "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" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hastscript": ["hastscript@6.0.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="], + + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], + + "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + + "htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], + + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + + "is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="], + + "is-alphanumerical": ["is-alphanumerical@1.0.4", "", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-decimal": ["is-decimal@1.0.4", "", {}, "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-hexadecimal": ["is-hexadecimal@1.0.4", "", {}, "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-text-path": ["is-text-path@2.0.0", "", { "dependencies": { "text-extensions": "^2.0.0" } }, "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw=="], + + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "istextorbinary": ["istextorbinary@9.5.0", "", { "dependencies": { "binaryextensions": "^6.11.0", "editions": "^6.21.0", "textextensions": "^6.11.0" } }, "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw=="], + + "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + + "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "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" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], + + "jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="], + + "jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="], + + "keytar": ["keytar@7.9.0", "", { "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" } }, "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ=="], + + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.kebabcase": ["lodash.kebabcase@4.1.1", "", {}, "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lodash.mergewith": ["lodash.mergewith@4.6.2", "", {}, "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="], + + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + + "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="], + + "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="], + + "lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="], + + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], + + "lodash.upperfirst": ["lodash.upperfirst@4.3.1", "", {}, "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], + + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], + + "meow": ["meow@12.1.1", "", {}, "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="], + + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + + "node-sarif-builder": ["node-sarif-builder@3.3.1", "", { "dependencies": { "@types/sarif": "^2.1.7", "fs-extra": "^11.1.1" } }, "sha512-8z5dAbhpxmk/WRQHXlv4V0h+9Y4Ugk+w08lyhV/7E/CQX9yDdBc3025/EG+RSMJU2aPFh/IQ7XDV7Ti5TLt/TA=="], + + "normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="], + + "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], + + "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], + + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "parse-semver": ["parse-semver@1.1.1", "", { "dependencies": { "semver": "^5.1.0" } }, "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], + + "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], + + "path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], + + "path-type": ["path-type@6.0.0", "", {}, "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "prebuild-install": ["prebuild-install@7.1.3", "", { "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" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "rc-config-loader": ["rc-config-loader@4.1.3", "", { "dependencies": { "debug": "^4.3.4", "js-yaml": "^4.1.0", "json5": "^2.2.2", "require-from-string": "^2.0.2" } }, "sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w=="], + + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], + + "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], + + "react-syntax-highlighter": ["react-syntax-highlighter@15.6.6", "", { "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.30.0", "refractor": "^3.6.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw=="], + + "read": ["read@1.0.7", "", { "dependencies": { "mute-stream": "~0.0.4" } }, "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ=="], + + "read-pkg": ["read-pkg@9.0.1", "", { "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" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "refractor": ["refractor@3.6.0", "", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "secretlint": ["secretlint@10.2.2", "", { "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": "./bin/secretlint.js" }, "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "side-channel": ["side-channel@1.1.0", "", { "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" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "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" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], + + "slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "structured-source": ["structured-source@4.0.0", "", { "dependencies": { "boundary": "^2.0.0" } }, "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA=="], + + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], + + "table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="], + + "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "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" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "terminal-link": ["terminal-link@4.0.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "supports-hyperlinks": "^3.2.0" } }, "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA=="], + + "text-extensions": ["text-extensions@2.4.0", "", {}, "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "textextensions": ["textextensions@6.11.0", "", { "dependencies": { "editions": "^6.21.0" } }, "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ=="], + + "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "typed-rest-client": ["typed-rest-client@1.8.11", "", { "dependencies": { "qs": "^6.9.1", "tunnel": "0.0.6", "underscore": "^1.12.1" } }, "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + + "underscore": ["underscore@1.13.7", "", {}, "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g=="], + + "undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], + + "url-join": ["url-join@4.0.1", "", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + + "version-range": ["version-range@4.15.0", "", {}, "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + + "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + + "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + + "yazl": ["yazl@2.5.1", "", { "dependencies": { "buffer-crc32": "~0.2.3" } }, "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw=="], + + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@commitlint/format/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@commitlint/load/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@commitlint/types/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@secretlint/formatter/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@secretlint/formatter/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@textlint/linter-formatter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + + "@textlint/linter-formatter/pluralize": ["pluralize@2.0.0", "", {}, "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw=="], + + "decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "find-up/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], + + "hastscript/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], + + "hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="], + + "hastscript/property-information": ["property-information@5.6.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA=="], + + "hastscript/space-separated-tokens": ["space-separated-tokens@1.1.5", "", {}, "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="], + + "htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "keytar/node-addon-api": ["node-addon-api@4.3.0", "", {}, "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="], + + "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "mdast-util-mdx-jsx/parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + + "normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "parse-semver/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], + + "prebuild-install/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "read-pkg/parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], + + "read-pkg/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], + + "stringify-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "@secretlint/formatter/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@textlint/linter-formatter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "hastscript/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "mdast-util-mdx-jsx/parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "mdast-util-mdx-jsx/parse-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "mdast-util-mdx-jsx/parse-entities/character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + + "mdast-util-mdx-jsx/parse-entities/is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "mdast-util-mdx-jsx/parse-entities/is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "mdast-util-mdx-jsx/parse-entities/is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "mdast-util-mdx-jsx/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + } +} diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..48e6504 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1 @@ +install.registry = "https://registry.npmjs.org/" diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..84dcb12 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +}; diff --git a/docs/API_DEPENDENCIES.md b/docs/API_DEPENDENCIES.md index c1c96d1..5ea78fc 100644 --- a/docs/API_DEPENDENCIES.md +++ b/docs/API_DEPENDENCIES.md @@ -1,258 +1,42 @@ -# Goose API Dependencies +# ACP Protocol Reference -This document outlines the API dependencies between the VS Code extension and the `goosed` server process. It serves as a contract that defines the expected endpoints, request/response formats, headers, and environment variables required for proper operation. +This extension communicates with goose via the **[Agent Communication Protocol (ACP)](https://agentclientprotocol.com/overview/introduction)** using JSON-RPC 2.0 over stdin/stdout. -## Authentication +## Protocol -All API requests from the extension to the `goosed` server include the following header for authentication: +- **Transport**: stdin/stdout with ndjson framing +- **Spec**: [agentclientprotocol.com](https://agentclientprotocol.com) +- **Version Requirement**: goose >= 1.16.0 -- `X-Secret-Key`: A cryptographically secure random key generated during server startup and transmitted to the `goosed` process via the `GOOSE_SERVER__SECRET_KEY` environment variable. The server validates this key to ensure requests are coming from the authorized extension process. +## Extension-Specific Implementation -## Required Endpoints +### Methods Used -### Server Status +| Method | Purpose | +|--------|---------| +| `initialize` | Initialize ACP connection | +| `session/new` | Create new chat session | +| `session/load` | Load session with history | +| `session/prompt` | Send user message | -**GET /status** -- **Purpose**: Check if the server is running and ready to handle requests -- **Request Headers**: `X-Secret-Key` -- **Response**: HTTP 200 OK if the server is ready -- **Usage**: Called by `ApiClient.checkStatus()` to verify server health after startup and during operation +### Notifications Handled -### Chat Communication +| Notification | Purpose | +|--------------|---------| +| `session/update` | Streaming response chunks | +| `session/cancel` | Cancel generation | -**POST /reply** -- **Purpose**: Send messages to the AI and receive a streamed response -- **Request Headers**: `X-Secret-Key`, `Accept: text/event-stream`, `Content-Type: application/json` -- **Request Body**: - ```json - { - "messages": [ - { - "id": "string", - "role": "user" | "assistant" | "system", - "content": "string", - "timestamp": "ISO-8601 date string", - "codeReferences": [ - { - "id": "string", - "content": "string", - "fileName": "string", - "language": "string", - "startLine": number, - "endLine": number - } - ] - } - ], - "session_id": "string (optional)", - "session_working_dir": "string" - } - ``` -- **Response**: Server-sent events (SSE) stream with chunks of the AI response -- **Usage**: Called by `ApiClient.streamChatResponse()` when a user sends a message in the chat interface +### Content Blocks -**POST /reply/ask** -- **Purpose**: Simplified endpoint for quick, non-streamed AI responses -- **Request Headers**: `X-Secret-Key`, `Content-Type: application/json` -- **Request Body**: - ```json - { - "prompt": "string", - "session_id": "string (optional)", - "session_working_dir": "string" - } - ``` -- **Response**: - ```json - { - "text": "string" - } - ``` -- **Usage**: Called by `ApiClient.ask()` for simple, one-off questions that don't need streaming +When sending `session/prompt`, context chips are sent as `resource_link` blocks: -**POST /reply/confirm** -- **Purpose**: Confirm or reject a tool call requested by the AI -- **Request Headers**: `X-Secret-Key`, `Content-Type: application/json` -- **Request Body**: - ```json - { - "id": "string", - "confirmed": boolean - } - ``` -- **Response**: JSON response indicating success -- **Usage**: Called by `ApiClient.confirmToolCall()` when the user approves/rejects a tool execution +```typescript +{ type: "resource_link", uri: string, range?: LineRange } +``` -### Agent Configuration +## Implementation Files -**GET /agent/versions** -- **Purpose**: Retrieve available agent versions -- **Request Headers**: `X-Secret-Key` -- **Response**: - ```json - { - "available_versions": ["string", "string", ...], - "default_version": "string" - } - ``` -- **Usage**: Called by `ServerManager` during startup to determine which agent version to use - -**GET /agent/providers** -- **Purpose**: Retrieve available AI providers -- **Request Headers**: `X-Secret-Key` -- **Response**: - ```json - { - "providers": [ - { - "name": "string", - "is_configured": boolean, - "metadata": { - "name": "string", - "display_name": "string", - "description": "string", - "default_model": "string", - "known_models": [ - { - "name": "string", - "context_limit": integer - } - ], - "model_doc_link": "string", - "config_keys": [ - { - "key": "string", - "display_name": "string", - "description": "string", - "is_secret": boolean, - "required": boolean - } - ] - } - } - ] - } - ``` -- **Usage**: Called by `ApiClient.getProviders()` to check available AI providers - -**POST /agent** -- **Purpose**: Configure the agent with a specific provider, model, and version -- **Request Headers**: `X-Secret-Key`, `Content-Type: application/json` -- **Request Body**: - ```json - { - "provider": "string", - "model": "string (optional)", - "version": "string (optional)" - } - ``` -- **Response**: JSON response with agent configuration details -- **Usage**: Called by `ServerManager.configureAgent()` during server startup to set up the AI agent - -**POST /agent/update_provider** -- **Purpose**: Update the agent's provider and model configuration. -- **Request Headers**: `X-Secret-Key`, `Content-Type: application/json` -- **Request Body**: - ```json - { - "provider": "string", - "model": "string (optional)", - "version": "string (optional)" - } - ``` -- **Response**: JSON response indicating success, or potentially an empty body on success (e.g., HTTP 200/204 with Content-Length: 0). -- **Usage**: Called by `ServerManager.configureAgent()` during server startup after fetching versions and adding extensions. - -**POST /agent/prompt** -- **Purpose**: Set or extend the base system prompt for the agent -- **Request Headers**: `X-Secret-Key`, `Content-Type: application/json` -- **Request Body**: - ```json - { - "extension": "string" - } - ``` - *Note: The `extension` field contains the text to be appended to or used as the system prompt.* -- **Response**: JSON response indicating success or failure -- **Usage**: Called by `ServerManager.configureAgent()` during server startup after the agent is created/configured, to provide initial context (e.g., VS Code environment details). - -**POST /extensions/add** -- **Purpose**: Add extensions to the agent for additional capabilities -- **Request Headers**: `X-Secret-Key`, `Content-Type: application/json` -- **Request Body**: - ```json - { - "type": "builtin", - "name": "string" - } - ``` -- **Response**: JSON response indicating success -- **Usage**: Called by `ApiClient.addExtension()` to enhance the agent with additional capabilities like the "developer" extension - -### Session Management - -**GET /sessions** -- **Purpose**: List available chat sessions -- **Request Headers**: `X-Secret-Key` -- **Response**: Array of session metadata objects or an object with a `sessions` property containing the array -- **Usage**: Called by `ApiClient.listSessions()` to populate the session list in the UI - -**GET /sessions/{sessionId}** -- **Purpose**: Get chat history for a specific session -- **Request Headers**: `X-Secret-Key` -- **Path Parameters**: `sessionId` - Identifier of the session to retrieve -- **Response**: Session object with messages and metadata -- **Usage**: Called by `ApiClient.getSessionHistory()` when switching to an existing session - -**POST /sessions/new** -- **Purpose**: Create a new session (explicitly) -- **Request Headers**: `X-Secret-Key`, `Content-Type: application/json` -- **Request Body**: - ```json - { - "working_dir": "string", - "description": "string" - } - ``` -- **Response**: JSON response with the created session ID -- **Usage**: Called by `ApiClient.createSession()` when a user explicitly creates a new session - -**POST /sessions/{sessionId}/rename** -- **Purpose**: Rename/update a session's description -- **Request Headers**: `X-Secret-Key`, `Content-Type: application/json` -- **Path Parameters**: `sessionId` - Identifier of the session to rename -- **Request Body**: - ```json - { - "description": "string" - } - ``` -- **Response**: JSON response indicating success -- **Usage**: Called by `ApiClient.renameSession()` when a user renames a session - -**DELETE /sessions/{sessionId}** -- **Purpose**: Delete a session -- **Request Headers**: `X-Secret-Key` -- **Path Parameters**: `sessionId` - Identifier of the session to delete -- **Response**: JSON response indicating success -- **Usage**: Called by `ApiClient.deleteSession()` when a user deletes a session - -## Environment Variables - -The following environment variables are recognized and used by the `goosed` process: - -- **GOOSE_SERVER__SECRET_KEY**: Secret key for authenticating API requests. Set by the VS Code extension when launching the `goosed` process. - -## Error Handling - -All API endpoints are expected to: - -1. Return appropriate HTTP status codes (e.g., 200 for success, 400 for client errors, 500 for server errors) -2. Provide meaningful error messages in the response body for non-200 status codes -3. For streaming endpoints, emit proper error events in the server-sent events stream when applicable - -The extension's `ApiClient` handles these error responses by: -- Checking the response status code -- Attempting to parse error messages from the response body -- Logging detailed error information when in debug mode -- Propagating errors through the extension's event system or throwing caught exceptions +- `src/extension/jsonRpcClient.ts` - JSON-RPC client with ndjson framing +- `src/extension/subprocessManager.ts` - Process lifecycle management +- `src/extension/versionChecker.ts` - Version validation (>= 1.16.0) +- `src/shared/errors.ts` - Typed error handling diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 18e7bb5..b6d6c7a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,383 +1,23 @@ -# Goose VSCode Extension Architecture +# Architecture -This document outlines the architecture of the Goose VSCode extension. +For detailed architecture documentation, see the knowledge base: -## Components +- **[Overview & Quick Reference](../.rp1/context/index.md)** - Project summary, structure, key features +- **[System Architecture](../.rp1/context/architecture.md)** - Component diagrams, data flows, integration points +- **[Module Breakdown](../.rp1/context/modules.md)** - Detailed component responsibilities +- **[Implementation Patterns](../.rp1/context/patterns.md)** - Code conventions, error handling, UI patterns +- **[Domain Concepts](../.rp1/context/concept_map.md)** - Terminology and business concepts -* **VS Code UI:** The standard VS Code interface where the user interacts (editor, activity bar, context menus). -* **Extension Host:** The Node.js process run by VS Code where the main extension logic resides (`src/extension.ts`, `src/server/*`, `src/utils/*`). -* **Chat Webview:** An isolated iframe running the chat UI (`webview-ui/`). It communicates with the Extension Host via message passing. -* **`goosed` Process:** The external daemon process (part of Goose Desktop) spawned and managed by the Extension Host. It exposes an HTTP API for AI interactions. -* **Goose AI Backend:** The actual AI models and infrastructure that `goosed` communicates with (not directly part of the extension's architecture but the ultimate destination). +## Quick Summary -## Sequence Diagram: Ask Goose about this code - -```mermaid -sequenceDiagram - autonumber - participant User - participant VSCodeUI as VS Code UI - participant ExtHost as Extension Host - participant ChatWebview as Chat Webview - participant GoosedAPI as `goosed` Process API - participant AIBackend as Goose AI Backend - - Note over User, VSCodeUI: User may or may not select code in editor - - User->>VSCodeUI: Right-click -> "Ask Goose about this code"
OR
Press Cmd/Ctrl+Shift+G - activate VSCodeUI - - VSCodeUI->>ExtHost: Execute command 'goose.askAboutSelection' - deactivate VSCodeUI - activate ExtHost - - Note over ExtHost: (extension.ts:activate registers command) - Note over ExtHost: Checks if text is selected and selection size - - alt No selection - ExtHost->>ExtHost: Get entire file content - ExtHost->>ChatWebview: Post message (ADD_CODE_REFERENCE, focus) - Note over ChatWebview: Webview UI adds code reference chip for whole file - else Selection >= 100 lines - ExtHost->>ExtHost: CodeReferenceManager gets selected text/range - ExtHost->>ChatWebview: Post message (ADD_CODE_REFERENCE, focus) - Note over ChatWebview: Webview UI adds code reference chip for selection - else Selection < 100 lines - ExtHost->>ChatWebview: Post message (PREPARE_MESSAGE_WITH_CODE, focus) - Note over ChatWebview: Webview stores code for next message,
no visible chip added - end - - activate ChatWebview - Note over ChatWebview: Chat input is focused - - User->>ChatWebview: Types question and presses Enter - - alt With code reference chip (No selection or >= 100 lines) - ChatWebview->>ExtHost: Post message (SEND_CHAT_MESSAGE) with text & code ref - else With prepended code (< 100 lines) - ChatWebview->>ExtHost: Post message (SEND_CHAT_MESSAGE) with text & prepended code - end - - deactivate ChatWebview - activate ExtHost - - Note over ExtHost: (GooseViewProvider receives message) - ExtHost->>ExtHost: ChatProcessor prepares request - activate ExtHost #LightSkyBlue - alt With code reference chip - ExtHost->>GoosedAPI: POST /chat (stream request with message, code ref, session) - else With prepended code - Note over ExtHost: Format code as markdown in message - ExtHost->>GoosedAPI: POST /chat (stream request with formatted message, session) - end - deactivate ExtHost #LightSkyBlue - activate GoosedAPI - - GoosedAPI->>AIBackend: Forward request - activate AIBackend - - AIBackend-->>GoosedAPI: Stream AI response chunks - deactivate AIBackend - - GoosedAPI-->>ExtHost: Stream response chunks - deactivate GoosedAPI - activate ExtHost - - Note over ExtHost: (ChatProcessor receives stream chunks) - ExtHost->>ChatWebview: Post message (CHAT_RESPONSE chunk) - activate ChatWebview - Note over ChatWebview: Webview UI appends chunk to response message - - loop Response Streaming - GoosedAPI-->>ExtHost: Stream response chunks - activate GoosedAPI - deactivate GoosedAPI - activate ExtHost - ExtHost->>ChatWebview: Post message (CHAT_RESPONSE chunk) - activate ChatWebview - deactivate ChatWebview - end - - GoosedAPI-->>ExtHost: Stream finished signal - activate GoosedAPI - deactivate GoosedAPI - - ExtHost->>ChatWebview: Post message (GENERATION_FINISHED) - deactivate ExtHost - deactivate ExtHost - activate ChatWebview - Note over ChatWebview: Webview UI finalizes message display - - deactivate ChatWebview +The extension is a **thin UI bridge** connecting VS Code to Goose via the **Agent Communication Protocol (ACP)**: ``` - -## Communication - -* **VS Code UI <-> Extension Host:** Standard VS Code API (Commands, Context Menus, WebviewViewProvider). -* **Extension Host <-> Chat Webview:** Asynchronous message passing (`postMessage`, `onDidReceiveMessage`), including messages like `WEBVIEW_READY` for initialization. Defined message types in `src/types/messages.ts` (previously mentioned `src/common-types.ts` which might be outdated or a general reference). - - * **Message Structure:** Messages exchanged, particularly those handled by `ChatProcessor` and stored by `SessionManager`, utilize a structured content model. A `Message` object (defined in `src/types/messages.ts`) contains a `content` array, where each element is a `MessageContent` part. Key parts include: - * `TextPart`: Represents plain text segments, which can include user-typed markdown. - * `CodeContextPart`: Represents a snippet of code context. It extends the `CodeReference` interface (from `src/utils/codeReferenceManager.ts`) and includes `filePath`, `fileName`, `startLine`, `endLine`, `selectedText`, and `languageId`, along with a `type: 'code_context'`. This allows for structured transfer of code information and distinct UI rendering. - * Other parts like `ImageContent`, `ToolRequestMessageContent`, etc., are also supported. - This structured approach ensures that different types of content within a single message (e.g., a user's question and an accompanying code snippet) are clearly delineated for processing, API serialization, and UI rendering. - -* **Extension Host <-> `goosed` Process:** - * Process Management: Spawning/killing the `goosed` executable using Node.js `child_process`. - * API Communication: HTTP requests from the Extension Host's `ApiClient` to the local HTTP server run by `goosed`. A secret key is used for authentication. -* **`goosed` Process <-> Goose AI Backend:** Internal communication protocol (details outside the scope of this extension). - -## Server Daemon Management - -The extension is responsible for starting, stopping, and monitoring the external `goosed` process. This is primarily handled by the `ServerManager` class within the Extension Host. - -```mermaid -sequenceDiagram - participant User - participant VSCodeUI as VS Code UI - participant ExtHost as Extension Host - participant GoosedProcess as `goosed` Process - participant ChatWebview as Chat Webview - - %% Automatic Startup on Activation %% - Note over ExtHost: Extension Activation - activate ExtHost - ExtHost->>ExtHost: Create ServerManager - activate ExtHost #Azure - ExtHost->>ExtHost: serverManager.start() - deactivate ExtHost #Azure - activate ExtHost #LightSkyBlue - ExtHost->>ExtHost: Generate Secret Key - ExtHost->>ExtHost: Find `goosed` binary path - ExtHost->>GoosedProcess: Spawn process (with config, secret key) - activate GoosedProcess - Note over ExtHost, GoosedProcess: `startGoosed` function handles spawning - GoosedProcess-->>ExtHost: Process started, port assigned - ExtHost->>ExtHost: Create ApiClient (with port, secret key) - - %% Status Check and Agent Configuration %% - ExtHost->>GoosedProcess: GET /status (check server health) - GoosedProcess-->>ExtHost: 200 OK (server ready) - - %% Agent Version Check %% - ExtHost->>GoosedProcess: GET /agent/versions - GoosedProcess-->>ExtHost: Available versions and default version - - %% Add 'developer' Extension %% - ExtHost->>GoosedProcess: POST /extensions/add (body: {name: "developer", type: "builtin"}) - GoosedProcess-->>ExtHost: Extension added successfully - - %% Configure Agent (Provider/Model/Version) %% - ExtHost->>GoosedProcess: POST /agent/update_provider (body: {provider, model, version}) - GoosedProcess-->>ExtHost: Agent configuration success - - %% Set Initial System Prompt %% - ExtHost->>GoosedProcess: POST /agent/prompt (body: {extension: vscodePrompt}) - GoosedProcess-->>ExtHost: System prompt set successfully - - ExtHost->>ExtHost: Set Status = RUNNING - ExtHost->>ChatWebview: Post message (SERVER_STATUS, running) - deactivate ExtHost #LightSkyBlue - activate ChatWebview - Note over ChatWebview: UI updates status indicator - deactivate ChatWebview - deactivate ExtHost - - %% Manual Stop Command %% - User->>VSCodeUI: Execute Command "Goose: Stop Server" - activate VSCodeUI - VSCodeUI->>ExtHost: Execute command 'goose.stopServer' - deactivate VSCodeUI - activate ExtHost - ExtHost->>ExtHost: serverManager.stop() - activate ExtHost #LightSkyBlue - ExtHost->>GoosedProcess: Send SIGTERM/kill signal - GoosedProcess-->>ExtHost: Process exits - deactivate GoosedProcess - ExtHost->>ExtHost: Set Status = STOPPED - ExtHost->>ChatWebview: Post message (SERVER_STATUS, stopped) - deactivate ExtHost #LightSkyBlue - activate ChatWebview - Note over ChatWebview: UI updates status indicator - deactivate ChatWebview - deactivate ExtHost - - %% Manual Start Command %% - User->>VSCodeUI: Execute Command "Goose: Start Server" - activate VSCodeUI - VSCodeUI->>ExtHost: Execute command 'goose.startServer' - deactivate VSCodeUI - activate ExtHost - ExtHost->>ExtHost: serverManager.start() - Note right of ExtHost: Same flow as automatic startup... - deactivate ExtHost - -``` - -**Key Steps:** - -1. **Activation:** When the extension activates, it creates a `ServerManager` instance and calls its `start` method. -2. **Starting:** - * The `ServerManager` generates a unique secret key. - * It locates the `goosed` executable (likely bundled or found in the Goose Desktop installation). - * It spawns `goosed` as a child process, passing configuration like the working directory and the secret key (e.g., via command-line flag or environment variable). - * Once the process confirms it's running and listening on a port, the `ServerManager` creates an `ApiClient` configured with the port and secret key. - * It checks server health via `/status` endpoint. - * It configures the agent by: - * Fetching available versions from `/agent/versions`. - * Adding the "developer" extension via `POST /extensions/add`. - * Updating the agent's provider, model, and version via `POST /agent/update_provider`. - * Setting the initial system prompt via `POST /agent/prompt`. - * The status is updated to `RUNNING` and propagated to the Chat Webview. -3. **Stopping:** - * The `stop` method (triggered by command or extension deactivation) sends a termination signal to the `goosed` process. - * It waits for the process to exit. - * The status is updated to `STOPPED` and propagated to the Chat Webview. -4. **Status Monitoring:** The `ServerManager` monitors the child process. If it crashes or exits unexpectedly, the status is updated (e.g., to `ERROR` or `STOPPED`) and the change is reflected in the UI. - -## Security Considerations - -Security is managed through several layers: - -1. **Server Binding:** The `goosed` process's API server is explicitly bound to `127.0.0.1` (localhost). This prevents other machines on the network from accessing the API directly. - -2. **Secret Key Management:** - * **Generation:** A cryptographically strong, 32-byte random secret key is generated by the Extension Host (`ServerManager`) each time the `goosed` process is started (`crypto.randomBytes`). This key is ephemeral and exists only for the lifetime of that specific `goosed` instance. - * **Transmission:** The secret key is passed securely from the Extension Host to the spawned `goosed` process via an **environment variable** (`GOOSE_SERVER__SECRET_KEY`). This method is significantly safer than using command-line arguments, as environment variables are generally not visible to other users or processes on the system (though processes running as the *same user* could potentially inspect them). - * **Usage:** The `ApiClient` within the Extension Host includes this secret key in its requests to the `goosed` API (e.g., potentially in an `Authorization` header or similar mechanism). The `goosed` server validates this key to ensure requests are coming from the managing extension process. - -3. **Process Isolation:** The Chat Webview runs in an isolated iframe sandbox, limiting its direct access to VS Code APIs or the user's system. Communication happens strictly through controlled message passing with the Extension Host. - -4. **Binary Execution:** The extension relies on executing the `goosed` binary, which is expected to be provided by the main Goose Desktop application installation. Standard security considerations around executing external binaries apply. -5. **Logging:** The `ApiClient` can be configured to log request/response bodies (excluding secrets) via the `goose.logging.logSensitiveRequests` setting. This is disabled by default to protect potentially sensitive user data. - -## Session Management - -Chat history is managed through sessions. Users can create new sessions, switch between existing sessions, and view the history associated with each. Session management involves the Chat Webview, Extension Host (specifically `GooseViewProvider` and `SessionManager`), and the `goosed` API. - -```mermaid -sequenceDiagram - autonumber - participant User - participant ChatWebview as Chat Webview - participant ExtHost as Extension Host (ViewProvider) - participant SessionMgr as SessionManager - participant ApiClient as ApiClient - participant GoosedAPI as `goosed` Process API - - %% Fetching Session List on Startup/Request %% - Note over ChatWebview, ExtHost: Webview requests session list (e.g., on load) - ChatWebview->>ExtHost: Post message (GET_SESSIONS) - activate ExtHost - ExtHost->>SessionMgr: fetchSessions() - activate SessionMgr - SessionMgr->>ApiClient: listSessions() - activate ApiClient - ApiClient->>GoosedAPI: GET /sessions - activate GoosedAPI - GoosedAPI-->>ApiClient: List of SessionMetadata - deactivate GoosedAPI - ApiClient-->>SessionMgr: SessionMetadata[] - deactivate ApiClient - SessionMgr-->>ExtHost: SessionMetadata[] - deactivate SessionMgr - ExtHost->>ChatWebview: Post message (SESSIONS_LIST) - deactivate ExtHost - activate ChatWebview - Note over ChatWebview: UI populates session list/dropdown - deactivate ChatWebview - - alt User selects an existing session - %% Switching to an Existing Session %% - User->>ChatWebview: Selects an existing session from UI - activate ChatWebview - ChatWebview->>ExtHost: Post message (SWITCH_SESSION, sessionId) - deactivate ChatWebview - activate ExtHost - ExtHost->>SessionMgr: switchSession(sessionId) - activate SessionMgr - SessionMgr->>SessionMgr: loadSession(sessionId) - activate SessionMgr #LightSkyBlue - SessionMgr->>ApiClient: getSessionHistory(sessionId) - activate ApiClient - ApiClient->>GoosedAPI: GET /sessions/{sessionId}/history - activate GoosedAPI - GoosedAPI-->>ApiClient: Session object (incl. messages) - deactivate GoosedAPI - ApiClient-->>SessionMgr: Session - deactivate ApiClient - Note over SessionMgr: Updates internal currentSession - SessionMgr-->>SessionMgr: Session - deactivate SessionMgr #LightSkyBlue - SessionMgr-->>ExtHost: true (switch success) - deactivate SessionMgr - Note over ExtHost: Gets loaded Session object from SessionMgr - ExtHost->>ChatWebview: Post message (SESSION_LOADED, sessionId, messages) - deactivate ExtHost - activate ChatWebview - Note over ChatWebview: UI loads and displays messages for the session - deactivate ChatWebview - - else User creates a new session - %% Creating a New Session (Locally First) %% - User->>ChatWebview: Clicks "New Chat" button - activate ChatWebview - ChatWebview->>ExtHost: Post message (CREATE_SESSION, description?) - deactivate ChatWebview - activate ExtHost - ExtHost->>SessionMgr: createSession(workingDir, description?) - activate SessionMgr - Note over SessionMgr: Generates new local sessionId (e.g., timestamp-based) - Note over SessionMgr: Creates new Session object in memory - Note over SessionMgr: Updates internal sessions list and currentSession - SessionMgr-->>ExtHost: newSessionId - deactivate SessionMgr - Note over ExtHost: Gets the new (empty) Session object from SessionMgr - ExtHost->>ChatWebview: Post message (SESSION_LOADED, newSessionId, []) - ExtHost->>ChatWebview: Post message (SESSIONS_LIST, updatedList) - deactivate ExtHost - activate ChatWebview - Note over ChatWebview: UI clears message area, updates session list - deactivate ChatWebview - - %% Sending First Message in New Session (Implicit Backend Creation) %% - User->>ChatWebview: Types first message and presses Enter - activate ChatWebview - ChatWebview->>ExtHost: Post message (SEND_CHAT_MESSAGE, text, newSessionId) - deactivate ChatWebview - activate ExtHost - Note over ExtHost: (GooseViewProvider routes to ChatProcessor) - ExtHost->>ApiClient: streamChatResponse(messages, newSessionId, workingDir) - Note over ExtHost: (Via ChatProcessor.sendMessage -> sendChatRequest) - activate ApiClient - ApiClient->>GoosedAPI: POST /reply (body: { messages: [...], session_id: newSessionId, session_working_dir: ... }) - deactivate ApiClient - activate GoosedAPI - Note over GoosedAPI: Backend sees newSessionId, creates session
using this ID and persists the message. - GoosedAPI-->>ApiClient: Stream AI response chunks... - deactivate GoosedAPI - ApiClient-->>ExtHost: Stream response chunks... - activate ApiClient - deactivate ApiClient - ExtHost->>ChatWebview: Post message (CHAT_RESPONSE chunk)... - deactivate ExtHost - activate ChatWebview - Note over ChatWebview: UI displays response... - deactivate ChatWebview - - end - +VS Code Webview (React) ←→ Extension Host ←→ goose subprocess (JSON-RPC 2.0 over stdin/stdout) ``` -**Key Points:** - -1. **Fetching:** The list of available sessions (`SessionMetadata`) is fetched from the `goosed` API (`GET /sessions`) via the `ApiClient` when requested by the webview. -2. **Loading:** When switching to an existing session, the full session details including message history (`Session`) are fetched from the `goosed` API (`GET /sessions/{sessionId}/history`). - * **Message Reconstruction:** Upon loading, the `SessionManager.loadSession` method processes the messages. If user messages contain `TextPart` content that matches a specific serialization format (used by `ChatProcessor` to send code context to the API, e.g., `// Meta: ... \`\`\`...\`\`\``), `SessionManager` attempts to parse these `TextPart`s and reconstruct them into proper `CodeContextPart` objects. This ensures that the rest of the extension (including the UI) works with the rich, structured message content, even if the backend API might have stored a serialized version. -3. **Creation & Synchronization:** - * New sessions are first created locally within the `SessionManager` using a frontend-generated ID (e.g., timestamp-based). The UI reflects this new session immediately. - * The session is implicitly created on the backend when the *first message* for that session is sent via a `POST` request to the `/reply` endpoint. The request body includes the frontend-generated `session_id`. - * The backend uses this provided `session_id` to create and persist the new session. The frontend does not need to update its ID. -4. **State:** The `SessionManager` keeps track of the available session metadata and the currently loaded session details in memory. -5. **Communication:** Session-related actions are triggered by the user in the Chat Webview, which sends messages to the Extension Host. The Extension Host interacts with the `SessionManager`, which in turn uses the `ApiClient` to communicate with the `goosed` API for fetching/loading existing sessions or implicitly creating new ones via `/reply`, and updates the Webview with the results. +Key architectural decisions: +- **No business logic** in the extension - pure orchestration +- **Message-driven** webview communication (24 typed message types) +- **Version-gated activation** (requires goose >= 1.16.0) +- **fp-ts** for typed async error handling diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 43a3800..5c346a6 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,27 +1,63 @@ -# Goose VSCode Extension Development +# Goose VS Code Extension Development -This document provides information for developers working on the Goose VSCode extension. +This document provides information for developers working on the Goose VS Code extension. For user documentation, see the main [README.md](../README.md). For architectural details, see [ARCHITECTURE.md](./ARCHITECTURE.md). -## Dev Set up +## Dev Setup 1. Clone the repository -2. Navigate to the project root directory -3. Install dependencies: `npm install` (this installs devDependencies like `rimraf` used by build and test scripts) -4. Install webview dependencies: `cd webview-ui && npm install && cd ..` -5. Build the extension and webview: `npm run compile` -6. Open the project root in VSCode: `code .` -7. Press F5 to start debugging (this will usually run the `compile` script automatically based on `.vscode/launch.json` preLaunchTask). +2. Install [Bun](https://bun.sh/) if not already installed +3. Navigate to the project root directory +4. Install dependencies: `bun install` +5. Build the extension: `bun run build` +6. Open the project in VS Code: `code .` +7. Press F5 to start debugging + +## Project Structure + +``` +src/ +├── extension/ # VS Code extension host (Node.js) +│ ├── extension.ts # Main entry point +│ ├── subprocessManager.ts +│ ├── jsonRpcClient.ts +│ ├── sessionManager.ts +│ ├── webviewProvider.ts +│ └── ... +├── webview/ # React chat UI (sandboxed iframe) +│ ├── App.tsx +│ ├── bridge.ts +│ ├── hooks/ +│ └── components/ +└── shared/ # Shared types between extension/webview + ├── messages.ts + ├── types.ts + └── errors.ts +``` ## Build Process -The extension uses a multi-step build process defined in `package.json` scripts: +The extension uses Bun for building. All scripts are defined in `package.json`: + +### Build Commands + +| Command | Description | +|---------|-------------| +| `bun run build` | Full build: extension + webview + CSS | +| `bun run build:extension` | Build extension only | +| `bun run build:webview` | Build webview React app | +| `bun run build:webview:css` | Build Tailwind CSS | +| `bun run dev` | Watch mode for extension development | +| `bun run clean` | Remove dist directory | -1. **`npm run build:extension`**: Uses `esbuild` to bundle the main extension code (`src/extension.ts` and its direct/indirect imports) into a single file (`out/extension.js`) with a sourcemap. This significantly reduces the package size and improves load times. The `vscode` module is marked as external as it's provided by the VS Code runtime. Dependencies listed in `devDependencies` (like `yaml`) should be correctly bundled. -2. **`npm run compile:tests`**: Uses the TypeScript compiler (`tsc`) based on the specific `tsconfig.tests.json` configuration. This compiles *only* the test files (`src/test/**/*.ts`) into JavaScript files within the `out/test/` directory, preserving the test file structure. This ensures test files are compiled separately from the main extension bundle and placed where the test runner expects them. -3. **`npm run build:webview`**: Navigates to the `webview-ui/` directory and runs its build process (using Vite) to create the optimized chat interface assets in `webview-ui/dist/`. (Note: `webview-ui` dependencies should be installed separately as part of the initial dev setup or by CI). -4. **`npm run compile`**: Orchestrates the above steps, running `build:extension`, then `compile:tests`, then `build:webview`. This is the main script used for building the entire extension before testing or packaging. It ensures the main bundle is created first, followed by the separate compilation of tests, and finally the webview build. +### Build Details + +1. **Extension Build** (`build:extension`): Uses Bun to bundle `src/extension/extension.ts` into `dist/extension.js` with CommonJS format for Node.js. The `vscode` module is external. + +2. **Webview Build** (`build:webview`): Uses Bun to bundle `src/webview/index.tsx` into `dist/webview/main.js` with minification. + +3. **CSS Build** (`build:webview:css`): Uses Tailwind CSS v4 to compile `src/webview/styles.css` into `dist/webview/styles.css`. ## Testing @@ -29,41 +65,64 @@ The extension uses a multi-step build process defined in `package.json` scripts: Run tests from the project root: -- Run all tests (lint, extension unit/integration, webview, package activation): `npm run test:all` -- Run only extension unit/integration tests: `npm run test` -- Run only webview tests: `npm run test:webview` -- Run packaged activation test: `npm run test:package` (Note: This runs against the packaged `.vsix` and verifies successful activation, catching bundling issues). +| Command | Description | +|---------|-------------| +| `bun test` | Run all tests | +| `bun test --watch` | Run tests in watch mode | ### Writing Tests -When writing new tests: +Tests are co-located with source files using the `*.test.ts` naming convention: + +- `src/extension/versionChecker.test.ts` +- `src/shared/fileReferenceParser.test.ts` +- `src/extension/jsonRpcClient.test.ts` + +Use the Bun test runner with the following pattern: -1. Add test cases to the appropriate test file -2. Follow the existing pattern using `suite()` for test groups and `test()` for individual tests -3. Use `assert` functions from the Node.js assert module for validations +```typescript +import { describe, it, expect } from "bun:test"; -### Debugging Tests +describe("MyModule", () => { + it("should do something", () => { + expect(result).toBe(expected); + }); +}); +``` + +## Linting and Formatting + +This project uses [Biome](https://biomejs.dev/) for linting and formatting. -To debug tests: +| Command | Description | +|---------|-------------| +| `bun run lint` | Run linter | +| `bun run lint:fix` | Run linter with auto-fix | +| `bun run format` | Format code | +| `bun run check` | Run both lint and format checks | +| `bun run check:fix` | Fix both lint and format issues | +| `bun run ci` | CI mode (fails on issues) | -1. Set breakpoints in your test files -2. Use the "Extension Tests" launch configuration from `.vscode/launch.json` -3. Select "Debug Tests" from the Testing panel's menu +## Packaging -### WebView UI Testing +To create a `.vsix` package for local testing: + +```bash +bun run package +``` -For testing the webview UI components directly: +This will: -1. Navigate to the webview directory: `cd webview-ui` -2. Run tests: `npm run test` -3. Run type-checking: `npm run type-check` -4. Return to root: `cd ..` +1. Build the extension +2. Build the webview +3. Compile Tailwind CSS +4. Package with `vsce` ## Commit Message Guidelines -This project adheres to the **Conventional Commits** specification ([v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/)). All commit messages **must** follow this format to enable automated changelog generation and version bumping by `release-please`. +This project uses **Conventional Commits** ([v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/)) with [commitlint](https://commitlint.js.org/) enforced via Husky pre-commit hooks. -**Format:** +### Format ``` [optional scope]: @@ -73,95 +132,102 @@ This project adheres to the **Conventional Commits** specification ([v1.0.0](htt [optional footer(s)] ``` -**Common Types:** - -* `feat`: A new feature for the user (corresponds to `minor` in SemVer). -* `fix`: A bug fix for the user (corresponds to `patch` in SemVer). -* `perf`: A code change that improves performance (corresponds to `patch` in SemVer). -* `refactor`: A code change that neither fixes a bug nor adds a feature. -* `style`: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc). -* `test`: Adding missing tests or correcting existing tests. -* `build`: Changes that affect the build system or external dependencies (e.g., `npm`, `esbuild`, `vsce`). -* `ci`: Changes to our CI configuration files and scripts (e.g., GitHub Actions). -* `docs`: Documentation only changes. -* `chore`: Other changes that don't modify `src` or `test` files (e.g., updating dependencies). - -**Breaking Changes:** Indicate breaking changes by appending `!` after the type/scope (`feat!: ...`) or by adding `BREAKING CHANGE:` in the commit footer (corresponds to `major` in SemVer). - -**Examples:** - -* `feat(webview): add support for multiple chat sessions` -* `fix(server): prevent crash when goosed path is invalid` -* `docs: update architecture diagram for session management` -* `refactor(apiClient): simplify error handling logic` -* `chore(deps): update typescript to 5.8.2` -* `ci: add automated changelog generation step` -* `feat(api)!: change chat endpoint structure` - -## Release Process (Automated) - -This project uses **`release-please`** to automate the release process based on **Conventional Commits**. - -**Workflow:** - -1. **Development:** Developers push features/fixes to branches and create Pull Requests (PRs) targeting the `main` branch. -2. **Conventional Commits:** All commits merged into `main` **must** follow the [Conventional Commits](#commit-message-guidelines) format. -3. **Release PR Creation:** Upon merging commits to `main`, the `Release Please` GitHub Action (`.github/workflows/ci.yml`) runs automatically. - * It analyzes commits since the last release tag (`vscode-v*`). - * It determines the correct semantic version bump (major, minor, or patch). - * It creates or updates a special "Release PR". This PR contains: - * Version bumps in `package.json`, `.release-please-manifest.json`, and `webview-ui/package.json`. - * An updated `CHANGELOG.md` with entries generated from the conventional commit messages. -4. **Review Release PR:** Review the automatically generated Release PR: - * Verify the version bump is correct. - * Check that the `CHANGELOG.md` entries accurately reflect the changes. -5. **Merge Release PR:** Merge the Release PR into `main`. -6. **Tagging and Publishing:** Merging the Release PR automatically triggers the following: - * `release-please` creates a Git tag (e.g., `vscode-v0.2.0`) on the merge commit. - * The tag push triggers the `release` job in the GitHub Actions workflow (`.github/workflows/ci.yml`). - * The `release` job: - * Checks out the code at the new tag. - * Installs dependencies using `npm ci`. - * Builds the extension (`npm run compile`). - * Packages the extension into a `.vsix` file (`dist/vscode-goose-vX.Y.Z.vsix`). - * Creates a GitHub Release associated with the tag, uploading the `.vsix` file as an asset. - * Publishes the `.vsix` file to the VS Code Marketplace (if `VSCE_PAT` secret is configured). - -**Commit Type Impact:** - -The type of Conventional Commit used when merging changes into `main` determines the version bump and whether the change appears in the `CHANGELOG.md`. - -| Commit Type Prefix | SemVer Bump Triggered | Appears in CHANGELOG.md? | Example | -| :----------------- | :-------------------- | :----------------------- | :------------------------------------------- | -| `feat` | Minor (0.x.0 -> 0.y.0) | Yes (under Features) | `feat: add dark mode toggle` | -| `fix` | Patch (0.0.x -> 0.0.y) | Yes (under Bug Fixes) | `fix: correct typo in error message` | -| `perf` | Patch (0.0.x -> 0.0.y) | Yes (under Performance) | `perf: optimize rendering loop` | -| `feat!` / `fix!` | Major (x.y.z -> Y.0.0) | Yes (under BREAKING CHANGES) | `feat!: change API endpoint structure` | -| `refactor` | None | No | `refactor: simplify internal logic` | -| `style` | None | No | `style: format code with prettier` | -| `test` | None | No | `test: add unit tests for parser` | -| `build` | None | No | `build: update esbuild configuration` | -| `ci` | None | No | `ci: fix workflow trigger condition` | -| `docs` | None | No | `docs: update README installation steps` | -| `chore` | None | No | `chore: update non-essential dependencies` | - -* **Changelog:** Only `feat`, `fix`, `perf`, and breaking changes (`!`) are included in the automatically generated `CHANGELOG.md` within the Release PR. -* **Publishing:** A new version is published to the VS Code Marketplace *only* when a Release PR is merged, which triggers the tag creation and the `release` workflow job. Commits like `docs`, `chore`, `refactor`, etc., merged to `main` will *not* trigger a release or appear in the changelog, although they will be included in the *next* release if a `feat` or `fix` commit triggers one later. - -**Manual Packaging (for testing):** - -While the official release is automated, you can still build a local `.vsix` package for testing purposes using the scripts defined in `package.json`: - -- `npm run package:dist`: Builds and packages into the `dist/` folder. -- `npm run package:skip-tests`: Skips tests, builds, and packages into the root folder. +### Common Types -```bash -# Example: Create a package in dist/ for local testing -npm run package:dist +| Type | Description | SemVer Impact | +|------|-------------|---------------| +| `feat` | New feature | Minor | +| `fix` | Bug fix | Patch | +| `perf` | Performance improvement | Patch | +| `refactor` | Code change (no feature/fix) | None | +| `style` | Formatting changes | None | +| `test` | Adding/fixing tests | None | +| `build` | Build system changes | None | +| `ci` | CI configuration changes | None | +| `docs` | Documentation only | None | +| `chore` | Other maintenance | None | + +### Breaking Changes + +Indicate breaking changes with `!` after type/scope: + +``` +feat!: change API endpoint structure ``` -**Note:** The `scripts/release.sh` script is **deprecated** for the main release flow but might be kept for local utility if needed (see Phase 3 tasks). +### Examples + +``` +feat(webview): add support for multiple chat sessions +fix(subprocess): prevent crash when goose path is invalid +docs: update architecture diagram +refactor(jsonrpc): simplify error handling +chore(deps): update typescript to 5.8.0 +``` + +## Configuration + +The extension exposes these VS Code settings: + +| Setting | Type | Default | Description | +|---------|------|---------|-------------| +| `goose.binaryPath` | string | `""` | Path to goose binary (empty = auto-detect) | +| `goose.logLevel` | enum | `"info"` | Logging level: error, warn, info, debug | + +## Commands + +Registered commands accessible via Command Palette: + +| Command | ID | Description | +|---------|-----|-------------| +| Goose: Show Logs | `goose.showLogs` | Open output channel | +| Goose: Restart | `goose.restart` | Restart goose subprocess | +| Send to Goose | `goose.sendSelectionToChat` | Send selection to chat (Cmd+Shift+G) | + +## Debugging + +### Extension Debugging + +1. Set breakpoints in `src/extension/*.ts` files +2. Press F5 to launch Extension Development Host +3. The debugger will attach automatically + +### Webview Debugging + +1. In Extension Development Host, open the Goose panel +2. Open Command Palette > "Developer: Open Webview Developer Tools" +3. Use Chrome DevTools to debug React components + +### Subprocess Debugging + +View goose communication: + +1. Run "Goose: Show Logs" command +2. Set `goose.logLevel` to `debug` for verbose output + +## Dependencies + +### Runtime Dependencies + +| Package | Version | Purpose | +|---------|---------|---------| +| fp-ts | ^2.16.0 | Functional programming (Either, TaskEither) | +| react-markdown | ^10.1.0 | Markdown rendering | +| react-syntax-highlighter | ^15.6.1 | Code syntax highlighting | +| remark-gfm | ^4.0.1 | GitHub Flavored Markdown | +| zod | ^3.23.0 | Runtime type validation | + +### Dev Dependencies + +| Package | Purpose | +|---------|---------| +| @biomejs/biome | Linting and formatting | +| @tailwindcss/cli | CSS framework | +| @vscode/vsce | Extension packaging | +| typescript | Type checking | +| husky | Git hooks | +| @commitlint/* | Commit message linting | ## Known Issues -Refer to the [GitHub issues page](https://github.com/block/vscode-goose/issues) for any known issues related to the VSCode extension. +Refer to the [GitHub issues page](https://github.com/block/vscode-goose/issues) for known issues. diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index d5c0b53..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import tsParser from "@typescript-eslint/parser"; - -export default [{ - files: ["**/*.ts"], -}, { - plugins: { - "@typescript-eslint": typescriptEslint, - }, - - languageOptions: { - parser: tsParser, - ecmaVersion: 2022, - sourceType: "module", - }, - - rules: { - "@typescript-eslint/naming-convention": ["warn", { - selector: "import", - format: ["camelCase", "PascalCase"], - }], - - curly: "warn", - eqeqeq: "warn", - "no-throw-literal": "warn", - semi: "warn", - }, -}]; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 1947ecb..0000000 --- a/package-lock.json +++ /dev/null @@ -1,10710 +0,0 @@ -{ - "name": "vscode-goose", - "version": "0.1.22", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "vscode-goose", - "version": "0.1.22", - "license": "Apache-2.0", - "dependencies": { - "typescript": "^5.8.2", - "yaml": "^2.4.5" - }, - "devDependencies": { - "@commitlint/cli": "^19.8.0", - "@commitlint/config-conventional": "^19.8.0", - "@types/mocha": "^10.0.10", - "@types/node": "^20.17.24", - "@types/react": "^18.2.45", - "@types/react-dom": "^18.2.18", - "@types/sinon": "^17.0.4", - "@types/vscode": "^1.95.0", - "@typescript-eslint/eslint-plugin": "^8.25.0", - "@typescript-eslint/parser": "^8.25.0", - "@vitejs/plugin-react": "^4.2.1", - "@vscode/test-cli": "^0.0.10", - "@vscode/test-electron": "^2.4.1", - "autoprefixer": "^10.4.16", - "esbuild": "^0.25.3", - "eslint": "^9.21.0", - "husky": "^9.1.7", - "npm-run-all": "^4.1.5", - "postcss": "^8.4.32", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "release-please": "^17.0.0", - "rimraf": "^6.0.1", - "sinon": "^19.0.4", - "tailwindcss": "^3.3.6", - "ts-mockito": "^2.6.1", - "vite": "^6.3.4" - }, - "engines": { - "vscode": "^1.95.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@commitlint/cli": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", - "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/format": "^19.8.1", - "@commitlint/lint": "^19.8.1", - "@commitlint/load": "^19.8.1", - "@commitlint/read": "^19.8.1", - "@commitlint/types": "^19.8.1", - "tinyexec": "^1.0.0", - "yargs": "^17.0.0" - }, - "bin": { - "commitlint": "cli.js" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/config-conventional": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", - "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^19.8.1", - "conventional-changelog-conventionalcommits": "^7.0.2" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/config-validator": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", - "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^19.8.1", - "ajv": "^8.11.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/ensure": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz", - "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^19.8.1", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.upperfirst": "^4.3.1" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/execute-rule": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", - "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/format": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz", - "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^19.8.1", - "chalk": "^5.3.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/is-ignored": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", - "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^19.8.1", - "semver": "^7.6.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/lint": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", - "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/is-ignored": "^19.8.1", - "@commitlint/parse": "^19.8.1", - "@commitlint/rules": "^19.8.1", - "@commitlint/types": "^19.8.1" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/load": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", - "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/config-validator": "^19.8.1", - "@commitlint/execute-rule": "^19.8.1", - "@commitlint/resolve-extends": "^19.8.1", - "@commitlint/types": "^19.8.1", - "chalk": "^5.3.0", - "cosmiconfig": "^9.0.0", - "cosmiconfig-typescript-loader": "^6.1.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/message": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", - "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/parse": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", - "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^19.8.1", - "conventional-changelog-angular": "^7.0.0", - "conventional-commits-parser": "^5.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/read": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", - "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/top-level": "^19.8.1", - "@commitlint/types": "^19.8.1", - "git-raw-commits": "^4.0.0", - "minimist": "^1.2.8", - "tinyexec": "^1.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/resolve-extends": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", - "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/config-validator": "^19.8.1", - "@commitlint/types": "^19.8.1", - "global-directory": "^4.0.1", - "import-meta-resolve": "^4.0.0", - "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/rules": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", - "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/ensure": "^19.8.1", - "@commitlint/message": "^19.8.1", - "@commitlint/to-lines": "^19.8.1", - "@commitlint/types": "^19.8.1" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/to-lines": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", - "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/top-level": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz", - "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^7.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@commitlint/types": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", - "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/conventional-commits-parser": "^5.0.0", - "chalk": "^5.3.0" - }, - "engines": { - "node": ">=v18" - } - }, - "node_modules/@conventional-commits/parser": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@conventional-commits/parser/-/parser-0.4.1.tgz", - "integrity": "sha512-H2ZmUVt6q+KBccXfMBhbBF14NlANeqHTXL4qCL6QGbMzrc4HDXyzWuxPxPNbz71f/5UkR5DrycP5VO9u7crahg==", - "dev": true, - "license": "ISC", - "dependencies": { - "unist-util-visit": "^2.0.3", - "unist-util-visit-parents": "^3.1.1" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", - "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.14.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@google-automations/git-file-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-automations/git-file-utils/-/git-file-utils-3.0.0.tgz", - "integrity": "sha512-e+WLoKR0TchIhKsSDOnd/su171eXKAAdLpP2tS825UAloTgfYus53kW8uKoVj9MAsMjXGXsJ2s1ASgjq81xVdA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@octokit/rest": "^20.1.1", - "@octokit/types": "^13.0.0", - "minimatch": "^5.1.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@google-automations/git-file-utils/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@iarna/toml": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jsep-plugin/assignment": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", - "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - }, - "peerDependencies": { - "jsep": "^0.4.0||^1.0.0" - } - }, - "node_modules/@jsep-plugin/regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", - "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - }, - "peerDependencies": { - "jsep": "^0.4.0||^1.0.0" - } - }, - "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/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz", - "integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.1.0", - "@octokit/request": "^8.4.1", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", - "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", - "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^8.4.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.4.4-cjs.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz", - "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.7.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "5" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", - "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "5" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.3.2-cjs.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz", - "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.8.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^5" - } - }, - "node_modules/@octokit/request": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", - "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^9.0.6", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", - "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.1.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest": { - "version": "20.1.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz", - "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/core": "^5.0.2", - "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", - "@octokit/plugin-request-log": "^4.0.0", - "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", - "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz", - "integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz", - "integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz", - "integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz", - "integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz", - "integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz", - "integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz", - "integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz", - "integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz", - "integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz", - "integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz", - "integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz", - "integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz", - "integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz", - "integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz", - "integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz", - "integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz", - "integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz", - "integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz", - "integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz", - "integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/conventional-commits-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", - "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz", - "integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==", - "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/npm-package-arg": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.4.tgz", - "integrity": "sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.23", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", - "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/sinon": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz", - "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sinonjs__fake-timers": "*" - } - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", - "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/vscode": { - "version": "1.100.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz", - "integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "16.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", - "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", - "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/type-utils": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", - "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.33.1", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.1.tgz", - "integrity": "sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.26.10", - "@babel/plugin-transform-react-jsx-self": "^7.25.9", - "@babel/plugin-transform-react-jsx-source": "^7.25.9", - "@rolldown/pluginutils": "1.0.0-beta.9", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" - } - }, - "node_modules/@vscode/test-cli": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.10.tgz", - "integrity": "sha512-B0mMH4ia+MOOtwNiLi79XhA+MLmUItIC8FckEuKrVAVriIuSWjt7vv4+bF8qVFiNFe4QRfzPaIZk39FZGWEwHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mocha": "^10.0.2", - "c8": "^9.1.0", - "chokidar": "^3.5.3", - "enhanced-resolve": "^5.15.0", - "glob": "^10.3.10", - "minimatch": "^9.0.3", - "mocha": "^10.2.0", - "supports-color": "^9.4.0", - "yargs": "^17.7.2" - }, - "bin": { - "vscode-test": "out/bin.mjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vscode/test-electron": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", - "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "jszip": "^3.10.1", - "ora": "^8.1.0", - "semver": "^7.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "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-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true, - "license": "MIT" - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "retry": "0.13.1" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "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/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, - "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" - }, - "engines": { - "node": ">=14.14.0" - } - }, - "node_modules/c8/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/c8/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/c8/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/c8/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/c8/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/c8/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/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/cliui/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/cliui/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/cliui/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/cliui/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/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/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/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/code-suggester": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/code-suggester/-/code-suggester-5.0.0.tgz", - "integrity": "sha512-/xyGfSM/hMYxl12kqoYoOwUm0D1uuVT2nWcMiTq2Fn5MLi+BlWkHq5AUvtniDJwVSdI3jgbK4AOzGws+v/dFPQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@octokit/rest": "^20.1.1", - "@types/yargs": "^16.0.0", - "async-retry": "^1.3.1", - "diff": "^5.0.0", - "glob": "^7.1.6", - "parse-diff": "^0.11.0", - "yargs": "^16.0.0" - }, - "bin": { - "code-suggester": "build/src/bin/code-suggester.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/code-suggester/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/code-suggester/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/code-suggester/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/code-suggester/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/code-suggester/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/code-suggester/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/code-suggester/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/code-suggester/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/code-suggester/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/code-suggester/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/code-suggester/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/code-suggester/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/code-suggester/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/code-suggester/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/conventional-changelog-angular": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", - "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", - "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-6.0.1.tgz", - "integrity": "sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-commits-filter": "^3.0.0", - "dateformat": "^3.0.3", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "meow": "^8.1.2", - "semver": "^7.0.0", - "split": "^1.0.1" - }, - "bin": { - "conventional-changelog-writer": "cli.js" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/conventional-changelog-writer/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/conventional-changelog-writer/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/conventional-changelog-writer/node_modules/meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-changelog-writer/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-changelog-writer/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/conventional-changelog-writer/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", - "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cosmiconfig-typescript-loader": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.1.0.tgz", - "integrity": "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "jiti": "^2.4.1" - }, - "engines": { - "node": ">=v18" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=9", - "typescript": ">=5" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "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.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/dargs": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", - "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" - }, - "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/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.165", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", - "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.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/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "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/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", - "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.5", - "@esbuild/android-arm": "0.25.5", - "@esbuild/android-arm64": "0.25.5", - "@esbuild/android-x64": "0.25.5", - "@esbuild/darwin-arm64": "0.25.5", - "@esbuild/darwin-x64": "0.25.5", - "@esbuild/freebsd-arm64": "0.25.5", - "@esbuild/freebsd-x64": "0.25.5", - "@esbuild/linux-arm": "0.25.5", - "@esbuild/linux-arm64": "0.25.5", - "@esbuild/linux-ia32": "0.25.5", - "@esbuild/linux-loong64": "0.25.5", - "@esbuild/linux-mips64el": "0.25.5", - "@esbuild/linux-ppc64": "0.25.5", - "@esbuild/linux-riscv64": "0.25.5", - "@esbuild/linux-s390x": "0.25.5", - "@esbuild/linux-x64": "0.25.5", - "@esbuild/netbsd-arm64": "0.25.5", - "@esbuild/netbsd-x64": "0.25.5", - "@esbuild/openbsd-arm64": "0.25.5", - "@esbuild/openbsd-x64": "0.25.5", - "@esbuild/sunos-x64": "0.25.5", - "@esbuild/win32-arm64": "0.25.5", - "@esbuild/win32-ia32": "0.25.5", - "@esbuild/win32-x64": "0.25.5" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", - "@eslint/plugin-kit": "^0.3.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/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/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/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/eslint/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/eslint/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/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/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-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "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.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/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/find-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", - "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^7.2.0", - "path-exists": "^5.0.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "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/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/git-raw-commits": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", - "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dargs": "^8.0.0", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "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/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "4.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/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/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "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" - }, - "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-text-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", - "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "text-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "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/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsep": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", - "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" - }, - "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/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, - "node_modules/jsonpath-plus": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", - "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsep-plugin/assignment": "^1.3.0", - "@jsep-plugin/regex": "^1.0.4", - "jsep": "^1.4.0" - }, - "bin": { - "jsonpath": "bin/jsonpath-cli.js", - "jsonpath-plus": "bin/jsonpath-cli.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", - "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.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/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/log-symbols/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/log-symbols/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/log-symbols/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/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "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", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minimist-options/node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/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/mocha/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/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/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/mocha/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/mocha/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/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/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/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "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/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/nise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", - "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" - } - }, - "node_modules/node-html-parser": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", - "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-select": "^5.1.0", - "he": "1.2.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/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/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm-run-all/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm-run-all/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/npm-run-all/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npm-run-all/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm-run-all/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/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/npm-run-all/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "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-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "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/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "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", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ora/node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-diff": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/parse-diff/-/parse-diff-0.11.1.tgz", - "integrity": "sha512-Oq4j8LAOPOcssanQkIjxosjATBIEJhCxMCxPhMu+Ci4wdNmAEdx0O+a7gzbR2PyKXgKPvRLIN5g224+dJAsKHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse-github-repo-url": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", - "integrity": "sha512-bSWyzBKqcSL4RrncTpGsEKoJ7H8a4L3++ifTAbTFeMHyq2wRV+42DGmQcHIrJIvdcacjIOxEuKH/w4tthF17gg==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/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/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "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": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", - "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.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "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/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/read-cache/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/release-please": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/release-please/-/release-please-17.1.0.tgz", - "integrity": "sha512-PLfrynx8oFsFiqYz06NJ3+OWkS1UJZoLMB+IfJf8XscSSYm7caYmbxwNMHdeNXj+u9fsDXWSPABcPBKmxsYjHw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@conventional-commits/parser": "^0.4.1", - "@google-automations/git-file-utils": "^3.0.0", - "@iarna/toml": "^3.0.0", - "@octokit/graphql": "^7.1.0", - "@octokit/request": "^8.3.1", - "@octokit/request-error": "^5.1.0", - "@octokit/rest": "^20.1.1", - "@types/npm-package-arg": "^6.1.0", - "@xmldom/xmldom": "^0.8.4", - "chalk": "^4.0.0", - "code-suggester": "^5.0.0", - "conventional-changelog-conventionalcommits": "^6.0.0", - "conventional-changelog-writer": "^6.0.0", - "conventional-commits-filter": "^3.0.0", - "detect-indent": "^6.1.0", - "diff": "^7.0.0", - "figures": "^3.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "js-yaml": "^4.0.0", - "jsonpath-plus": "^10.0.0", - "node-html-parser": "^6.0.0", - "parse-github-repo-url": "^1.4.1", - "semver": "^7.5.3", - "type-fest": "^3.0.0", - "typescript": "^4.6.4", - "unist-util-visit": "^2.0.3", - "unist-util-visit-parents": "^3.1.1", - "xpath": "^0.0.34", - "yaml": "^2.2.2", - "yargs": "^17.0.0" - }, - "bin": { - "release-please": "build/src/bin/release-please.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/release-please/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/release-please/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/release-please/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/release-please/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/release-please/node_modules/conventional-changelog-conventionalcommits": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz", - "integrity": "sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/release-please/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/release-please/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/release-please/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "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/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", - "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz", - "integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.7" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.42.0", - "@rollup/rollup-android-arm64": "4.42.0", - "@rollup/rollup-darwin-arm64": "4.42.0", - "@rollup/rollup-darwin-x64": "4.42.0", - "@rollup/rollup-freebsd-arm64": "4.42.0", - "@rollup/rollup-freebsd-x64": "4.42.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.42.0", - "@rollup/rollup-linux-arm-musleabihf": "4.42.0", - "@rollup/rollup-linux-arm64-gnu": "4.42.0", - "@rollup/rollup-linux-arm64-musl": "4.42.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.42.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-musl": "4.42.0", - "@rollup/rollup-linux-s390x-gnu": "4.42.0", - "@rollup/rollup-linux-x64-gnu": "4.42.0", - "@rollup/rollup-linux-x64-musl": "4.42.0", - "@rollup/rollup-win32-arm64-msvc": "4.42.0", - "@rollup/rollup-win32-ia32-msvc": "4.42.0", - "@rollup/rollup-win32-x64-msvc": "4.42.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup/node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "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-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "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/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sinon": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.5.tgz", - "integrity": "sha512-r15s9/s+ub/d4bxNXqIUmwp6imVSdTorIRaxoecYjqTVLZ8RuoXr/4EDGwIBo6Waxn7f2gnURX9zuhAfCwaF6Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.5", - "@sinonjs/samsam": "^8.0.1", - "diff": "^7.0.0", - "nise": "^6.1.1", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/sinon/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "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/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": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "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", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.padend": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", - "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tailwindcss/node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/text-extensions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", - "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.5" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicorn-magic": { - "version": "0.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/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/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" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "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/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "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_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xpath": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", - "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/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/yargs/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/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/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/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json index e6ed202..5d4a945 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,9 @@ "engines": { "vscode": "^1.95.0" }, - "categories": [ - "Other" - ], - "activationEvents": [], - "main": "./out/extension.js", + "categories": ["Other"], + "activationEvents": ["onStartupFinished"], + "main": "./dist/extension.js", "contributes": { "viewsContainers": { "activitybar": [ @@ -33,140 +31,99 @@ { "type": "webview", "id": "goose.chatView", - "name": "Chat" + "name": "" } ] }, "configuration": { "title": "Goose", "properties": { - "goose.enable": { - "type": "boolean", - "default": true, - "description": "Enable/disable the Goose extension" - }, - "goose.logging.enabled": { - "type": "boolean", - "default": false, - "markdownDescription": "Enable detailed logging to the **Goose Extension** output channel for debugging. Use the `Goose: Show Extension Logs` command to view logs." - }, - "goose.logging.level": { + "goose.binaryPath": { "type": "string", - "enum": [ - "DEBUG", - "INFO", - "WARN", - "ERROR" - ], - "default": "INFO", - "markdownDescription": "Set the logging level. `DEBUG` is verbose." - }, - "goose.logging.logSensitiveRequests": { - "type": "boolean", - "default": false, - "markdownDescription": "**WARNING:** Enable logging of potentially sensitive request/response bodies (e.g., chat content, code snippets) to the **Goose Extension** output channel. Secrets like API keys will still be redacted. Only enable this for detailed debugging with awareness of the data being logged." + "default": "", + "description": "Path to the goose binary. Leave empty for auto-detection." }, - "goose.server.path": { + "goose.logLevel": { "type": "string", - "default": "", - "description": "Custom path to the Goose Desktop installation. Leave empty for auto-detection." + "enum": ["error", "warn", "info", "debug"], + "default": "info", + "description": "Logging level for the Goose extension." } } }, "commands": [ { - "command": "goose.helloWorld", - "title": "Hello World" - }, - { - "command": "goose.startServer", - "title": "Goose: Start Server" + "command": "goose.showLogs", + "title": "Goose: Show Logs" }, { - "command": "goose.stopServer", - "title": "Goose: Stop Server" + "command": "goose.restart", + "title": "Goose: Restart" }, { - "command": "goose.askAboutSelection", - "title": "Ask Goose about this code" - }, + "command": "goose.sendSelectionToChat", + "title": "Send to Goose" + } + ], + "keybindings": [ { - "command": "goose.showLogs", - "title": "Goose: Show Extension Logs" + "command": "goose.sendSelectionToChat", + "key": "ctrl+shift+g", + "mac": "cmd+shift+g", + "when": "editorTextFocus" } ], "menus": { "editor/context": [ { - "command": "goose.askAboutSelection", - "when": "editorHasSelection", - "group": "goose" + "command": "goose.sendSelectionToChat", + "when": "editorTextFocus", + "group": "navigation" } ] - }, - "keybindings": [ - { - "command": "goose.askAboutSelection", - "key": "ctrl+alt+g", - "mac": "cmd+opt+g", - "when": "editorTextFocus" - } - ] + } }, "scripts": { - "vscode:prepublish": "npm run compile", - "package:dist": "npm run clean && mkdir -p dist && npm run compile && npx @vscode/vsce package --no-yarn -o dist/vscode-goose-$npm_package_version.vsix", - "package:skip-tests": "npm run clean && npm run compile && npx @vscode/vsce package --no-yarn", - "package:inspect": "npx @vscode/vsce ls --no-yarn", - "package:verify": "npx @vscode/vsce ls --tree", - "build:extension": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --sourcemap", - "watch:extension": "npm run build:extension -- --watch", - "compile:tests": "tsc -p tsconfig.tests.json", - "watch:tests": "npm run compile:tests -- --watch", - "compile": "npm run build:extension && npm run compile:tests && npm run build:webview", - "watch": "npm-run-all --parallel watch:extension watch:tests dev:webview", - "pretest": "npm run clean && npm run compile && npm run lint", - "lint": "eslint src", - "test": "vscode-test", - "test:package": "node ./out/test/test/runPackageTest.js", - "test:webview": "cd webview-ui && npm run test", - "test:all": "npm run test && npm run test:webview && npm run test:package", - "build:webview": "cd webview-ui && npm run build", - "dev:webview": "cd webview-ui && npm run dev", - "clean": "rimraf out && rimraf webview-ui/dist && rimraf *.vsix && rimraf dist", + "build": "bun build ./src/extension/extension.ts --outdir=./dist --target=node --format=cjs --external=vscode --sourcemap=linked && bun build ./src/webview/index.tsx --outdir=./dist/webview --entry-naming=main.[ext] --minify && tailwindcss -i ./src/webview/styles.css -o ./dist/webview/styles.css --minify", + "build:extension": "bun build ./src/extension/extension.ts --outdir=./dist --target=node --format=cjs --external=vscode --sourcemap=linked", + "build:webview": "bun build ./src/webview/index.tsx --outdir=./dist/webview --entry-naming=main.[ext] --minify", + "build:webview:css": "tailwindcss -i ./src/webview/styles.css -o ./dist/webview/styles.css --minify", + "dev": "bun build ./src/extension/extension.ts --outdir=./dist --target=node --format=cjs --external=vscode --sourcemap=linked --watch", + "test": "bun test", + "test:watch": "bun test --watch", + "lint": "bunx --bun biome lint", + "lint:fix": "bunx --bun biome lint --write", + "format": "bunx --bun biome format --write", + "check": "bunx --bun biome check", + "check:fix": "bunx --bun biome check --write", + "ci": "bunx --bun biome ci", + "package": "bun build ./src/extension/extension.ts --outdir=./dist --target=node --format=cjs --external=vscode --sourcemap=linked && bun build ./src/webview/index.tsx --outdir=./dist/webview --entry-naming=main.[ext] --minify && tailwindcss -i ./src/webview/styles.css -o ./dist/webview/styles.css --minify && vsce package", + "clean": "rm -rf dist", "prepare": "husky" }, + "dependencies": { + "fp-ts": "^2.16.0", + "react-markdown": "^10.1.0", + "react-syntax-highlighter": "^15.6.1", + "remark-gfm": "^4.0.1", + "zod": "^3.23.0" + }, "devDependencies": { - "@commitlint/cli": "^19.8.0", - "@commitlint/config-conventional": "^19.8.0", - "@types/mocha": "^10.0.10", - "@types/node": "^20.17.24", - "@types/react": "^18.2.45", - "@types/react-dom": "^18.2.18", - "@types/sinon": "^17.0.4", + "@biomejs/biome": "2.3.10", + "@commitlint/cli": "^19.6.0", + "@commitlint/config-conventional": "^19.6.0", + "@tailwindcss/cli": "^4.1.0", + "@types/node": "^20.17.0", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@types/react-syntax-highlighter": "^15.5.13", "@types/vscode": "^1.95.0", - "@typescript-eslint/eslint-plugin": "^8.25.0", - "@typescript-eslint/parser": "^8.25.0", - "@vitejs/plugin-react": "^4.2.1", - "@vscode/test-cli": "^0.0.10", - "@vscode/test-electron": "^2.4.1", + "@vscode/vsce": "^3.2.0", "autoprefixer": "^10.4.16", - "esbuild": "^0.25.3", - "eslint": "^9.21.0", - "husky": "^9.1.7", - "npm-run-all": "^4.1.5", + "husky": "^9.1.0", "postcss": "^8.4.32", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "release-please": "^17.0.0", - "rimraf": "^6.0.1", - "sinon": "^19.0.4", - "tailwindcss": "^3.3.6", - "ts-mockito": "^2.6.1", - "vite": "^6.3.4" - }, - "dependencies": { - "typescript": "^5.8.2", - "yaml": "^2.4.5" + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.8.0" } } diff --git a/release-please-config.json b/release-please-config.json index e00b597..9ddf5f8 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -10,10 +10,7 @@ "tag-separator": "vscode-v", "include-component-in-tag": false, "bootstrap-sha": "f4be3380275d551d7ebca1156e04d71f44036df4", - "extra-files": [ - "package.json", - "webview-ui/package.json" - ], + "extra-files": ["package.json"], "changelog-sections": [ { "type": "feat", diff --git a/resources/screenshot.gif b/resources/screenshot.gif new file mode 100644 index 0000000..58a8ee2 Binary files /dev/null and b/resources/screenshot.gif differ diff --git a/resources/screenshot.png b/resources/screenshot.png deleted file mode 100644 index 218ce24..0000000 Binary files a/resources/screenshot.png and /dev/null differ diff --git a/src/common-types/index.ts b/src/common-types/index.ts deleted file mode 100644 index c66e074..0000000 --- a/src/common-types/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Types shared between the Extension and the Webview UI - -export enum MessageType { - HELLO = 'hello', - GET_ACTIVE_EDITOR_CONTENT = 'getActiveEditorContent', - ACTIVE_EDITOR_CONTENT = 'activeEditorContent', - ERROR = 'error', - SERVER_STATUS = 'serverStatus', - CHAT_MESSAGE = 'chatMessage', - SEND_CHAT_MESSAGE = 'sendChatMessage', - AI_MESSAGE = 'aiMessage', - STOP_GENERATION = 'stopGeneration', - GENERATION_FINISHED = 'generationFinished', - CODE_REFERENCE = 'codeReference', - ADD_CODE_REFERENCE = 'addCodeReference', - REMOVE_CODE_REFERENCE = 'removeCodeReference', - GET_WORKSPACE_CONTEXT = 'getWorkspaceContext', - WORKSPACE_CONTEXT = 'workspaceContext', - CHAT_RESPONSE = 'chatResponse', - SESSIONS_LIST = 'sessionsList', - SESSION_LOADED = 'sessionLoaded', - SWITCH_SESSION = 'switchSession', - CREATE_SESSION = 'createSession', - RENAME_SESSION = 'renameSession', - DELETE_SESSION = 'deleteSession', - GET_SESSIONS = 'getSessions', - SERVER_EXIT = 'serverExit', - GET_SERVER_STATUS = 'getServerStatus', - RESTART_SERVER = 'restartServer', - FOCUS_CHAT_INPUT = 'focusChatInput', - PREPARE_MESSAGE_WITH_CODE = 'prepareMessageWithCode', // Added for <100 line selections - OPEN_SETTINGS_FILE = 'openSettingsFile', // Added for opening settings - SET_THEME = 'setTheme', // Added for Shiki theme synchronization - WEBVIEW_READY = 'webviewReady', // Added for webview readiness check - SET_EXTENSION_VERSION = 'setExtensionVersion', // Added for passing extension version to webview - RESOURCES_READY = 'resourcesReady' // Added for passing resource URIs to webview -} - -// Types copied from src/types/messages.ts to be shared - -export type Role = 'user' | 'assistant'; - -export interface TextContent { - type: 'text'; - text: string; - annotations?: Record; -} - -export interface ImageContent { - type: 'image'; - data: string; // Assuming base64 encoded data for webview compatibility - mimeType: string; - annotations?: Record; -} - -// Simplified Content type for webview - excluding tool-related types for now -// If tool interactions are needed in webview later, these can be added back carefully -export type SimpleContent = TextContent | ImageContent; - -// Basic Message structure shared between extension and webview -export interface Message { - id?: string; // Optional ID, might be assigned later - role: Role; - created: number; // Unix timestamp (seconds) - content: SimpleContent[]; // Use simplified content for now - // Removed tool-related fields for simplicity in shared type -} - -// Note: Tool-related types (ToolCall, ToolResult, etc.) are kept in src/types/messages.ts -// as they are primarily used within the extension host logic for now. -// If the webview needs to display or interact with tool calls/results directly, -// these types would need to be shared and potentially adapted. diff --git a/src/extension.ts b/src/extension.ts deleted file mode 100644 index 7e42c3b..0000000 --- a/src/extension.ts +++ /dev/null @@ -1,939 +0,0 @@ -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below -import * as vscode from 'vscode'; -import { ColorThemeKind } from 'vscode'; // Import ColorThemeKind -import * as path from 'path'; -import * as fs from 'fs'; -import { ServerManager, ServerStatus, ServerEvents } from './server/serverManager'; -import { ChatProcessor, ChatEvents } from './server/chat/chatProcessor'; -import { Message, getTextContent } from './types'; -// Import CodeReference type explicitly if needed, or rely on CodeReferenceManager export -import { CodeReferenceManager, CodeReference } from './utils/codeReferenceManager'; -import { WorkspaceContextProvider } from './utils/workspaceContextProvider'; -import { GooseCodeActionProvider } from './utils/codeActionProvider'; -import { SessionManager, SessionEvents } from './server/chat/sessionManager'; -// Import MessageType from common types -import { MessageType } from './common-types'; -// Import config reader function -import { getConfigFilePath } from './utils/configReader'; -// Import the new logger singleton -import { logger } from './utils/logger'; -// Import version utility -import { getExtensionVersion } from './utils/versionUtils'; - -// Create logger for the extension -// const logger = getLogger('Extension'); // OLD LOGGER REMOVED - -// Define line limit constant for prepending code vs. adding reference chip -const SELECTION_LINE_LIMIT_FOR_PREPEND = 100; - -// Interface for messages sent between extension and webview -interface WebviewMessage { - command: string; - [key: string]: any; // Additional properties -} - -/** - * Manages webview panels and sidebar view - */ -export class GooseViewProvider implements vscode.WebviewViewProvider { - public static readonly viewType = 'goose.chatView'; - private _view?: vscode.WebviewView; - private isWebviewReady = false; // Added for readiness check - private messageQueue: WebviewMessage[] = []; // Added for message queueing - private lastSentStatus: ServerStatus | undefined = undefined; // Added for status change check - private readonly _extensionUri: vscode.Uri; - private readonly _serverManager: ServerManager; - private readonly _chatProcessor: ChatProcessor; - private readonly _codeReferenceManager: CodeReferenceManager; - private readonly _workspaceContextProvider: WorkspaceContextProvider; - private readonly _sessionManager: SessionManager; - - constructor(extensionUri: vscode.Uri, serverManager: ServerManager, chatProcessor: ChatProcessor, sessionManager: SessionManager) { - this._extensionUri = extensionUri; - this._serverManager = serverManager; - this._chatProcessor = chatProcessor; - this._codeReferenceManager = CodeReferenceManager.getInstance(); - this._workspaceContextProvider = WorkspaceContextProvider.getInstance(); - this._sessionManager = sessionManager; - } - - /** - * Maps VS Code theme kind to a shiki theme identifier. - * Uses 'light-plus' and 'dark-plus' as they are built-in themes in shiki - * that correspond well to VS Code's default light and dark themes. - * @param kind The VS Code theme kind. - * @returns A shiki theme identifier string. - */ - public getShikiTheme(kind: ColorThemeKind): string { // Made public for listener access - switch (kind) { - case ColorThemeKind.Light: - case ColorThemeKind.HighContrastLight: - return 'light-plus'; // Shiki's equivalent for Light+ - case ColorThemeKind.Dark: - case ColorThemeKind.HighContrast: - return 'dark-plus'; // Shiki's equivalent for Dark+ - default: - return 'dark-plus'; // Default fallback - } - } - - resolveWebviewView( - webviewView: vscode.WebviewView, - context: vscode.WebviewViewResolveContext, - _token: vscode.CancellationToken - ) { - this._view = webviewView; - - // --- Detect and Map Theme --- - const activeTheme = vscode.window.activeColorTheme; - const shikiTheme = this.getShikiTheme(activeTheme.kind); - logger.info(`Detected VS Code theme kind: ${ColorThemeKind[activeTheme.kind]}, Mapped to shiki theme: ${shikiTheme}`); - // --- End Detect and Map Theme --- - - webviewView.webview.options = { - // Enable scripts in the webview - enableScripts: true, - // Restrict the webview to only load resources from allowed directories - localResourceRoots: [ - vscode.Uri.joinPath(this._extensionUri, 'out'), // For extension's JS/CSS - vscode.Uri.joinPath(this._extensionUri, 'webview-ui/dist'), // For webview's JS/CSS - vscode.Uri.joinPath(this._extensionUri, 'resources') // For images and other static assets - ] - }; - - webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); - - // Handle messages from the webview - webviewView.webview.onDidReceiveMessage(async (message) => { - await this._onDidReceiveMessage(message); - }); - - // Handle webview disposal - webviewView.onDidDispose(() => { - logger.info('Webview view disposed. Cleaning up resources.'); - this._view = undefined; - this.isWebviewReady = false; - this.messageQueue = []; // Clear any pending messages - }); - - // --- Add onDidChangeVisibility listener --- - webviewView.onDidChangeVisibility(() => { - if (this._view && this._view.visible) { - logger.debug('[GooseViewProvider] Webview became visible. Re-syncing state.'); - this.isWebviewReady = true; - - const currentStatus = this._serverManager.getStatus(); - this.postMessage({ command: MessageType.SERVER_STATUS, status: currentStatus }); - this.lastSentStatus = currentStatus; - - this._processMessageQueue(); - - const gooseIconUri = this._view?.webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'resources', 'goose-icon.png')); - this.postMessage({ command: MessageType.RESOURCES_READY, resources: { gooseIcon: gooseIconUri?.toString() } }); - - this.postMessage({ command: MessageType.SET_EXTENSION_VERSION, version: getExtensionVersion() }); - - const activeTheme = vscode.window.activeColorTheme; - this.postMessage({ command: MessageType.SET_THEME, theme: this.getShikiTheme(activeTheme.kind) }); - } else { - logger.debug('[GooseViewProvider] Webview became hidden.'); - } - }); - // --- End onDidChangeVisibility listener --- - - // Set up event listeners for server status changes - this._serverManager.on(ServerEvents.STATUS_CHANGE, (newStatus: ServerStatus) => { - if (newStatus !== this.lastSentStatus) { - // logger.debug(`Server status changed to ${newStatus}. Last sent was ${this.lastSentStatus}. Sending update.`); - this.postMessage({ - command: MessageType.SERVER_STATUS, - status: newStatus - }); - this.lastSentStatus = newStatus; - } - }); - - // Set up event listeners for chat events - this._chatProcessor.on(ChatEvents.MESSAGE_RECEIVED, (message: Message) => { - this.postMessage({ // Use the new postMessage method - command: MessageType.CHAT_RESPONSE, - message: message - }); - }); - - this._chatProcessor.on(ChatEvents.ERROR, (error: Error) => { - this.postMessage({ // Use the new postMessage method - command: MessageType.ERROR, - errorMessage: error.message - }); - }); - - this._chatProcessor.on(ChatEvents.FINISH, (message: Message, reason: string) => { - this.postMessage({ // Use the new postMessage method - command: MessageType.GENERATION_FINISHED, - message, - reason - }); - }); - - // Log that the view has been resolved - logger.info(`Webview view resolved with context: ${context.state}`); - - // Send initial messages (will be queued if webview is not ready yet) - const initialStatus = this._serverManager.getStatus(); - this.postMessage({ - command: MessageType.SERVER_STATUS, - status: initialStatus - }); - this.lastSentStatus = initialStatus; - - this.postMessage({ - command: MessageType.SET_THEME, - theme: shikiTheme - }); - } - - private async _onDidReceiveMessage(message: any) { - switch (message.command) { - case MessageType.WEBVIEW_READY: // Handle webview ready message - logger.info('Webview is ready. Processing message queue.'); - this.isWebviewReady = true; - this._processMessageQueue(); - - // Send extension version to webview - const extensionVersion = getExtensionVersion(); - logger.info(`Sending extension version to webview: ${extensionVersion}`); - this.postMessage({ - command: MessageType.SET_EXTENSION_VERSION, - version: extensionVersion - }); - - // Create and send proper webview URIs for resources - const gooseIconUri = this._view?.webview.asWebviewUri( - vscode.Uri.joinPath(this._extensionUri, 'resources', 'goose-icon.png') - ); - logger.info(`Generated webview URI for goose-icon.png: ${gooseIconUri}`); - this.postMessage({ - command: MessageType.RESOURCES_READY, - resources: { - gooseIcon: gooseIconUri?.toString() - } - }); - // Explicitly ensuring no SESSIONS_LIST is sent here. - // Webview initiates session fetching via GET_SESSIONS. - break; - - case MessageType.HELLO: - break; - - case MessageType.GET_ACTIVE_EDITOR_CONTENT: - this._getActiveEditorContent(); - break; - - case MessageType.SEND_CHAT_MESSAGE: - // Check if there's text, explicit code references (chips), or prepended code - if (message.text?.trim() || message.codeReferences?.length > 0 || message.prependedCode) { - try { - // Pass relevant data to the chat processor - // Note: The signature of sendMessage might need adjustment in Task 1.6 - await this._chatProcessor.sendMessage( - message.text, - message.codeReferences, // Existing code reference chips - message.prependedCode, // Pass the new prepended code data - message.messageId, - message.sessionId || this._sessionManager.getCurrentSessionId() - ); - } catch (error) { - logger.error('Error sending message to chat processor:', error); // Use logger - this.postMessage({ - command: MessageType.ERROR, - errorMessage: error instanceof Error ? error.message : String(error) - }); - } - } else { - logger.warn('Received SEND_CHAT_MESSAGE with no content.'); - } - break; - - case MessageType.STOP_GENERATION: - this._chatProcessor.stopGeneration(); - break; - - case MessageType.REMOVE_CODE_REFERENCE: - // Handle removing a code reference from the UI - if (message.id) { - logger.debug('Removing code reference with ID:', message.id); - // Send back confirmation to the webview to update its state - this.postMessage({ - command: MessageType.REMOVE_CODE_REFERENCE, - id: message.id - }); - } - break; - - case MessageType.GET_SESSIONS: - try { - const sessions = await this._sessionManager.fetchSessions(); - this.postMessage({ - command: MessageType.SESSIONS_LIST, - sessions - }); - } catch (error) { - logger.error('Error fetching sessions:', error); - this.postMessage({ - command: MessageType.ERROR, - error: 'Failed to fetch sessions' - }); - } - break; - - case MessageType.SWITCH_SESSION: - try { - const success = await this._sessionManager.switchSession(message.sessionId); - if (success) { - const session = this._sessionManager.getCurrentSession(); - if (session) { - this.postMessage({ - command: MessageType.SESSION_LOADED, - sessionId: session.session_id, - messages: session.messages - }); - } - } else { - this.postMessage({ - command: MessageType.ERROR, - error: 'Failed to switch session' - }); - } - } catch (error) { - logger.error('Error switching session:', error); - this.postMessage({ - command: MessageType.ERROR, - error: 'Failed to switch session' - }); - } - break; - - case MessageType.CREATE_SESSION: - try { - const workspaceDirectory = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; - if (!workspaceDirectory) { - this.postMessage({ - command: MessageType.ERROR, - error: 'No workspace folder found' - }); - return; - } - - const sessionId = await this._sessionManager.createSession( - workspaceDirectory, - message.description - ); - - if (sessionId) { - // Get the session data to send to the webview - const session = this._sessionManager.getCurrentSession(); - if (session) { - this.postMessage({ - command: MessageType.SESSION_LOADED, - sessionId: session.session_id, - messages: session.messages - }); - - // Also send the updated session list - this.postMessage({ - command: MessageType.SESSIONS_LIST, - sessions: this._sessionManager.getSessions() - }); - } - } else { - this.postMessage({ - command: MessageType.ERROR, - error: 'Failed to create session' - }); - } - } catch (error) { - logger.error('Error creating session:', error); - this.postMessage({ - command: MessageType.ERROR, - error: 'Failed to create session' - }); - } - break; - - case MessageType.RENAME_SESSION: - try { - // Get current session or let user pick one - let sessionId = this._sessionManager.getCurrentSessionId(); - let sessionDescription = ''; - - if (!sessionId) { - // Fetch available sessions - const sessions = await this._sessionManager.fetchSessions(); - - if (sessions.length === 0) { - vscode.window.showInformationMessage('No sessions available to rename.'); - return; - } - - // Create quick pick items from session list - const sessionItems = sessions.map(session => ({ - label: session.metadata.description || `Session ${session.id}`, - description: new Date(session.modified).toLocaleString(), - detail: `${session.metadata.message_count} messages`, - id: session.id - })); - - // Show quick pick menu - const selectedItem = await vscode.window.showQuickPick(sessionItems, { - placeHolder: 'Select a session to rename' - }); - - if (!selectedItem) { - return; // User cancelled - } - - sessionId = selectedItem.id; - sessionDescription = selectedItem.label; - } else { - // Get current session description - const currentSession = this._sessionManager.getCurrentSession(); - if (currentSession) { - sessionDescription = currentSession.metadata.description; - } - } - - // Get new description from user - const newDescription = await vscode.window.showInputBox({ - placeHolder: 'Enter a new description for the session', - prompt: 'This helps identify your session later', - value: sessionDescription - }); - - if (newDescription !== undefined && sessionId) { // User didn't cancel - const apiClient = this._serverManager.getApiClient(); - if (apiClient) { - const result = await apiClient.renameSession(sessionId, newDescription); - if (result) { - vscode.window.showInformationMessage(`Renamed session to: ${newDescription}`); - - // Refresh sessions and notify webview - await this._sessionManager.fetchSessions(); - this.postMessage({ - command: MessageType.SESSIONS_LIST, - sessions: this._sessionManager.getSessions() - }); - } else { - vscode.window.showErrorMessage('Failed to rename session'); - } - } - } - } catch (error) { - logger.error('Error renaming session:', error); - vscode.window.showErrorMessage('Failed to rename session'); - } - break; - - case MessageType.DELETE_SESSION: - try { - // Fetch available sessions - const sessions = await this._sessionManager.fetchSessions(); - - if (sessions.length === 0) { - vscode.window.showInformationMessage('No sessions available to delete.'); - return; - } - - // Create quick pick items from session list - const sessionItems = sessions.map(session => ({ - label: session.metadata.description || `Session ${session.id}`, - description: new Date(session.modified).toLocaleString(), - detail: `${session.metadata.message_count} messages`, - id: session.id - })); - - // Show quick pick menu - const selectedItem = await vscode.window.showQuickPick(sessionItems, { - placeHolder: 'Select a session to delete' - }); - - if (!selectedItem) { - return; // User cancelled - } - - // Confirm deletion - const confirmed = await vscode.window.showWarningMessage( - `Are you sure you want to delete "${selectedItem.label}"?`, - { modal: true }, - 'Delete' - ); - - if (confirmed === 'Delete') { - const apiClient = this._serverManager.getApiClient(); - if (apiClient) { - const result = await apiClient.deleteSession(selectedItem.id); - if (result) { - vscode.window.showInformationMessage(`Deleted session: ${selectedItem.label}`); - - // If we deleted the current session, switch to a new one - if (selectedItem.id === this._sessionManager.getCurrentSessionId()) { - // Create a new session or switch to another one - if (sessions.length > 1) { - // Find a different session to switch to - const differentSession = sessions.find(s => s.id !== selectedItem.id); - if (differentSession) { - await this._sessionManager.switchSession(differentSession.id); - } - } else { - // Create a new session if this was the only one - vscode.commands.executeCommand('goose.createSession'); - } - } - - // Refresh sessions and notify webview - await this._sessionManager.fetchSessions(); - this.postMessage({ - command: MessageType.SESSIONS_LIST, - sessions: this._sessionManager.getSessions() - }); - } else { - vscode.window.showErrorMessage('Failed to delete session'); - } - } - } - } catch (error) { - logger.error('Error deleting session:', error); - vscode.window.showErrorMessage('Failed to delete session'); - } - break; - - case MessageType.GET_SERVER_STATUS: - // logger.debug(`Received GET_SERVER_STATUS from webview. Current actual status: ${this._serverManager.getStatus()}. Sending.`); - this.postMessage({ - command: MessageType.SERVER_STATUS, - status: this._serverManager.getStatus() - }); - break; - - case MessageType.RESTART_SERVER: - logger.info('Restarting Goose server...'); - // Restart the server - this._serverManager.restart().then(success => { - // Send updated status - this.postMessage({ - command: MessageType.SERVER_STATUS, - status: this._serverManager.getStatus() - }); - - if (success) { - logger.info('Server restarted successfully'); - } else { - logger.error('Failed to restart server'); - this.postMessage({ - command: MessageType.ERROR, - errorMessage: 'Failed to restart the Goose server' - }); - } - }); - break; - - case MessageType.OPEN_SETTINGS_FILE: - try { - const configPath = getConfigFilePath(); // Use the imported function - if (configPath) { - const uri = vscode.Uri.file(configPath); - try { - const doc = await vscode.workspace.openTextDocument(uri); - await vscode.window.showTextDocument(doc); - logger.info(`Opened settings file: ${configPath}`); - } catch (err) { - logger.error(`Failed to open settings file at ${configPath}:`, err); - vscode.window.showErrorMessage(`Could not open settings file. It might not exist or there was a read error. Expected location: ${configPath}`); - } - } else { - logger.error('Could not determine the path to the settings file.'); - vscode.window.showErrorMessage('Could not determine the path to the Goose settings file for your OS.'); - } - } catch (error) { - logger.error('Error handling OPEN_SETTINGS_FILE:', error); - vscode.window.showErrorMessage('An unexpected error occurred while trying to open the settings file.'); - } - break; - - default: - logger.warn(`Unhandled message: ${message.command}`); - } - } - - private _getActiveEditorContent() { - const editor = vscode.window.activeTextEditor; - if (editor) { - const document = editor.document; - const content = document.getText(); - const fileName = document.fileName; - const languageId = document.languageId; - - this.postMessage({ - command: MessageType.ACTIVE_EDITOR_CONTENT, - content, - fileName, - languageId, - }); - } else { - this.postMessage({ - command: MessageType.ERROR, - errorMessage: 'No active editor found' - }); - } - } - - /** - * Processes the message queue, sending messages to the webview if it's ready. - */ - private _processMessageQueue() { - logger.debug(`Processing message queue. ${this.messageQueue.length} messages pending.`); - while (this.messageQueue.length > 0) { - const message = this.messageQueue.shift(); - if (message) { - logger.debug(`Dequeuing and sending message: ${message.command}`); - this._postMessageInternal(message); // Use internal method to send - } - } - } - - /** - * Internal method to actually send a message to the webview. - * Should only be called when the webview is known to be available. - */ - private async _postMessageInternal(message: WebviewMessage): Promise { - if (this._view && this._view.webview) { - try { - logger.debug(`Sending message to webview: ${message.command}`, message.command === 'aiMessageChunk' ? undefined : message); - const result = await this._view.webview.postMessage(message); - logger.debug(`Successfully posted message to webview: ${message.command}`); - if (message.command === 'aiMessage') { - logger.debug(`Sent full AI message with ID: ${message.message.id}`); - } - return true; - } catch (error) { - logger.error(`Error posting message ${message.command} to webview:`, error); - return false; - } - } else { - // This case should ideally be caught by the public postMessage queueing logic - logger.warn(`_postMessageInternal called but webview is not available. Message: ${message.command}`); - return false; - } - } - - /** - * Public method to send a message to the webview. - * Queues messages if the webview is not ready. - */ - public postMessage(message: WebviewMessage) { - if (this._view && this.isWebviewReady) { - this._postMessageInternal(message); - } else if (this._view) { - logger.debug(`Webview not ready, queuing message: ${message.command}`); - this.messageQueue.push(message); - } else { - logger.warn(`Webview panel is undefined, cannot send or queue message: ${message.command}`); - } - } - - - private _getHtmlForWebview(webview: vscode.Webview) { - // Path to the built webview UI - const distPath = vscode.Uri.joinPath(this._extensionUri, 'webview-ui', 'dist'); - const webviewDistPath = webview.asWebviewUri(distPath); - - // Get paths to CSS and JS files - const indexPath = path.join(this._extensionUri.fsPath, 'webview-ui', 'dist', 'index.html'); - - // Read the file - let indexHtml = fs.readFileSync(indexPath, 'utf8'); - - // Update the asset paths to be webview-friendly - indexHtml = indexHtml.replace( - /(href|src)="([^"]*)"/g, - (match, p1, p2) => { - // Skip external URLs and data URLs - if (p2.startsWith('http') || p2.startsWith('data:')) { - return match; - } - return `${p1}="${webviewDistPath.toString()}/${p2}"`; - } - ); - - return indexHtml; - } - - - - - -} - -// --- Exportable Handler Logic --- -// Added export -export async function handleAskAboutSelectionCommand( - provider: GooseViewProvider, - codeReferenceManager: CodeReferenceManager -) { - logger.info('Executing command: goose.askAboutSelection'); - - const editor = vscode.window.activeTextEditor; - if (!editor) { - logger.warn('No active text editor found.'); - vscode.window.showInformationMessage('No active text editor.'); - return; - } - - const document = editor.document; - const selection = editor.selection; - - let codeReferenceToSend: CodeReference | null = null; - let prepayloadToSend: any | null = null; // Payload for PREPARE_MESSAGE_WITH_CODE - let actionTaken = false; // Flag to track if we should focus - - if (selection.isEmpty) { - // Task 1.2: No selection - use whole file - const fileContent = document.getText(); - if (!fileContent || fileContent.trim() === '') { // Updated check - vscode.window.showInformationMessage('Active file is empty or contains only whitespace.'); - return; - } - const fileName = path.basename(document.fileName); - const lineCount = document.lineCount; - - if (lineCount >= SELECTION_LINE_LIMIT_FOR_PREPEND) { - // Use the new method for whole file referencing - codeReferenceToSend = codeReferenceManager.getCodeReferenceForEntireFile(document); - // getCodeReferenceForEntireFile already checks for empty/whitespace content - // and returns null, so no need for an additional check here if it's null. - // However, if it *is* null, we might want to inform the user, though it's covered by the initial check. - if (codeReferenceToSend) { - logger.info(`File >= ${SELECTION_LINE_LIMIT_FOR_PREPEND} lines, creating code reference chip for whole file.`); - actionTaken = true; - } else { - // This case should theoretically be caught by the initial fileContent.trim() check. - // If it still happens, it's an unexpected state or a bug in getCodeReferenceForEntireFile. - logger.warn('getCodeReferenceForEntireFile returned null for a non-empty, non-whitespace file.'); - vscode.window.showInformationMessage('Could not create a reference for the file.'); - return; - } - } else { - // Prepending whole file (already checked for empty/whitespace) - prepayloadToSend = { - content: fileContent, - fileName: fileName, - languageId: document.languageId, - startLine: 1, - endLine: lineCount > 0 ? lineCount : 1 - }; - logger.info(`File < ${SELECTION_LINE_LIMIT_FOR_PREPEND} lines, preparing message with whole file code.`); - actionTaken = true; - } - } else { - // User has made a selection - const selectedLines = selection.end.line - selection.start.line + 1; - - if (selectedLines >= SELECTION_LINE_LIMIT_FOR_PREPEND) { - // Task 1.3: >= 100 lines - use manager to create code reference chip - codeReferenceToSend = codeReferenceManager.getCodeReferenceFromSelection(); - if (!codeReferenceToSend) { - // This means selection was empty or whitespace only - vscode.window.showInformationMessage('Selected text is empty or contains only whitespace.'); - return; - } - logger.info(`Selection >= ${SELECTION_LINE_LIMIT_FOR_PREPEND} lines, creating code reference chip.`); - actionTaken = true; - } else { - // Task 1.4: < 100 lines - prepare message with code - const selectedText = document.getText(selection); - if (selectedText.trim() === '') { // Added check - vscode.window.showInformationMessage('Selected text is empty or contains only whitespace.'); - return; - } - // Construct a full CodeReference object for the prepayload - const filePath = document.uri.fsPath; - const fileName = path.basename(filePath); - prepayloadToSend = { - id: `${fileName}-${selection.start.line + 1}-${selection.end.line + 1}-${Date.now()}`, // Generate an ID - filePath: filePath, - fileName: fileName, - selectedText: selectedText, // Use selectedText instead of content - languageId: document.languageId, - startLine: selection.start.line + 1, - endLine: selection.end.line + 1 - } as CodeReference; // Cast to CodeReference to ensure all fields are there - logger.info(`Selection < ${SELECTION_LINE_LIMIT_FOR_PREPEND} lines (${prepayloadToSend.startLine}-${prepayloadToSend.endLine}), preparing message with code (as CodeReference).`); - actionTaken = true; - } - } - - // Send the appropriate message to the webview - if (codeReferenceToSend) { - provider.postMessage({ // Use the passed provider - command: MessageType.ADD_CODE_REFERENCE, - codeReference: codeReferenceToSend - }); - } else if (prepayloadToSend) { - provider.postMessage({ // Use the passed provider - command: MessageType.PREPARE_MESSAGE_WITH_CODE, - payload: prepayloadToSend - }); - } - - // Focus the chat view and input only if an action was taken - if (actionTaken) { - vscode.commands.executeCommand('goose.chatView.focus'); - provider.postMessage({ // Use the passed provider - command: MessageType.FOCUS_CHAT_INPUT - }); - } -} -// --- End Exportable Handler Logic --- - -export function activate(context: vscode.ExtensionContext) { - - // Initialize logger first - logger.info('Goose extension activating...'); - - // Get config path - const configFilePath = getConfigFilePath(); - logger.info(`Using config file at: ${configFilePath}`); - - // Create the ServerManager instance - const serverManager = new ServerManager(context); - - // Now create instances that depend on serverManager (assuming dependencies) - const sessionManager = new SessionManager(serverManager); - const chatProcessor = new ChatProcessor(serverManager); - - // Create the provider instance, passing managers - const provider = new GooseViewProvider( - context.extensionUri, - serverManager, // Pass serverManager - chatProcessor, // Pass chatProcessor - sessionManager // Pass sessionManager - ); - - // Get the code reference manager instance - const codeReferenceManager = CodeReferenceManager.getInstance(); - - // Register the view provider - const viewRegistration = vscode.window.registerWebviewViewProvider(GooseViewProvider.viewType, provider); - - // Register "Hello World" command - const helloDisposable = vscode.commands.registerCommand('goose.helloWorld', () => { - vscode.window.showInformationMessage('Hello World from Goose!'); - }); - - // Register Start/Stop commands for the server - const startServerDisposable = vscode.commands.registerCommand('goose.startServer', () => { - serverManager.start(); - }); - const stopServerDisposable = vscode.commands.registerCommand('goose.stopServer', () => { - serverManager.stop(); - }); - - // Register command to open settings - const openSettingsDisposable = vscode.commands.registerCommand('goose.openSettings', () => { - // Construct the search query for the extension settings - // The format `@ext:` is used to filter settings by a specific extension - vscode.commands.executeCommand('workbench.action.openSettings', '@ext:block.vscode-goose'); - }); - - // Register command to ask about selected code - // --->>> UPDATED: Use the exported handler - const askAboutSelectionDisposable = vscode.commands.registerCommand('goose.askAboutSelection', - () => handleAskAboutSelectionCommand(provider, codeReferenceManager) // Pass dependencies - ); - - // Register Code Action provider - const codeActionRegistration = vscode.languages.registerCodeActionsProvider( - { scheme: 'file' }, // Apply to all file types - new GooseCodeActionProvider() - ); - - // Register session management commands - const listSessionsDisposable = vscode.commands.registerCommand('goose.listSessions', async () => { - try { - logger.info('Executing command: goose.listSessions'); - const sessions = await sessionManager.fetchSessions(); - // Sessions are fetched and cached. - // The webview will request the list via GET_SESSIONS when it needs it. - // No need to proactively push to the webview here. - // if (provider) { - // provider.postMessage({ - // command: MessageType.SESSIONS_LIST, - // sessions - // }); - // } - } catch (error) { - logger.error('Error executing goose.listSessions command:', error); - vscode.window.showErrorMessage(`Failed to list sessions: ${error}`); - } - }); - - // --- Add Theme Change Listener --- - const themeChangeListener = vscode.window.onDidChangeActiveColorTheme(theme => { - const newShikiTheme = provider.getShikiTheme(theme.kind); - logger.info(`VS Code theme changed. New kind: ${ColorThemeKind[theme.kind]}, Mapped shiki theme: ${newShikiTheme}`); - // Use the new postMessage method - provider.postMessage({ - command: MessageType.SET_THEME, - theme: newShikiTheme - }); - }); - // --- End Theme Change Listener --- - - // --- Add Configuration Change Listener for Logging --- - const loggingConfigListener = vscode.workspace.onDidChangeConfiguration(event => { - if (event.affectsConfiguration('goose.logging.enabled') || event.affectsConfiguration('goose.logging.level')) { - logger.info('Logging configuration changed, updating logger...'); - logger.updateConfiguration(); - } - }); - // --- End Configuration Change Listener --- - - // Register command to show logs - const showLogsDisposable = vscode.commands.registerCommand('goose.showLogs', () => { - logger.info('Executing command: goose.showLogs'); - logger.showOutputChannel(); - }); - - // Add all disposables to the extension context's subscriptions - context.subscriptions.push( - viewRegistration, - helloDisposable, - startServerDisposable, - stopServerDisposable, - openSettingsDisposable, // Add open settings disposable - askAboutSelectionDisposable, - codeActionRegistration, - listSessionsDisposable, - themeChangeListener, - loggingConfigListener, - showLogsDisposable - ); - - logger.info('[Activate] About to call serverManager.start()'); // New log - // Start the server (if not already running and enabled) - serverManager.start().then(started => { - if (started) { - logger.info('[Activate] ServerManager.start() promise resolved true (started successfully).'); - } else { - logger.info('[Activate] ServerManager.start() promise resolved false (did not start, e.g., config error, already running).'); - } - }).catch(error => { - logger.error('[Activate] ServerManager.start() promise rejected with error:', error); - }); - - logger.info('Goose extension activated.'); -} - -// This method is called when your extension is deactivated -export function deactivate() { } diff --git a/src/extension/binaryDiscovery.test.ts b/src/extension/binaryDiscovery.test.ts new file mode 100644 index 0000000..c179c51 --- /dev/null +++ b/src/extension/binaryDiscovery.test.ts @@ -0,0 +1,569 @@ +import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'; +import * as E from 'fp-ts/Either'; +import { BinaryDiscoveryConfig } from '../shared/types'; + +// Track paths that should "exist" for our tests +let mockExistingPaths: Set = new Set(); + +// Mock fs module before importing binaryDiscovery +mock.module('fs', () => ({ + accessSync: (path: string, _mode?: number) => { + if (mockExistingPaths.has(path)) { + return undefined; + } + const error = new Error('ENOENT') as NodeJS.ErrnoException; + error.code = 'ENOENT'; + throw error; + }, + constants: { + X_OK: 1, + }, +})); + +// Import the module under test AFTER mocking fs +import { + checkPathExists, + discoverBinary, + expandPath, + findInPath, + findInPlatformPaths, + getAllSearchPaths, +} from './binaryDiscovery'; + +describe('binaryDiscovery', () => { + beforeEach(() => { + mockExistingPaths = new Set(); + }); + + afterEach(() => { + mockExistingPaths.clear(); + }); + + // Helper to add paths that "exist" + const addExistingPath = (path: string) => mockExistingPaths.add(path); + + describe('expandPath', () => { + const homeDir = '/home/testuser'; + const env: NodeJS.ProcessEnv = { + LOCALAPPDATA: 'C:\\Users\\Test\\AppData\\Local', + PROGRAMFILES: 'C:\\Program Files', + CUSTOM_VAR: '/custom/path', + }; + + describe('tilde expansion', () => { + test('expands ~ at start of path to home directory', () => { + const result = expandPath('~/.local/bin/goose', homeDir, env); + expect(result).toBe('/home/testuser/.local/bin/goose'); + }); + + test('expands ~ alone to home directory', () => { + const result = expandPath('~', homeDir, env); + expect(result).toBe('/home/testuser'); + }); + + test('does not expand ~ in middle of path', () => { + const result = expandPath('/some/path/~user/file', homeDir, env); + expect(result).toBe('/some/path/~user/file'); + }); + }); + + describe('environment variable expansion', () => { + test('expands %VAR% style variables', () => { + const result = expandPath('%LOCALAPPDATA%\\Goose\\goose.exe', homeDir, env); + expect(result).toBe('C:\\Users\\Test\\AppData\\Local\\Goose\\goose.exe'); + }); + + test('expands multiple %VAR% in same path', () => { + const result = expandPath('%LOCALAPPDATA%\\%CUSTOM_VAR%\\file', homeDir, env); + expect(result).toBe('C:\\Users\\Test\\AppData\\Local\\/custom/path\\file'); + }); + + test('replaces undefined variables with empty string', () => { + const result = expandPath('%UNDEFINED_VAR%\\file', homeDir, env); + expect(result).toBe('\\file'); + }); + + test('expands %PROGRAMFILES%', () => { + const result = expandPath('%PROGRAMFILES%\\Goose\\goose.exe', homeDir, env); + expect(result).toBe('C:\\Program Files\\Goose\\goose.exe'); + }); + }); + + describe('unchanged paths', () => { + test('returns absolute path unchanged', () => { + const result = expandPath('/usr/local/bin/goose', homeDir, env); + expect(result).toBe('/usr/local/bin/goose'); + }); + + test('returns Windows absolute path unchanged', () => { + const result = expandPath('C:\\Program Files\\goose.exe', homeDir, env); + expect(result).toBe('C:\\Program Files\\goose.exe'); + }); + + test('returns relative path unchanged', () => { + const result = expandPath('./bin/goose', homeDir, env); + expect(result).toBe('./bin/goose'); + }); + }); + }); + + describe('checkPathExists', () => { + test('returns true when file exists and is executable', () => { + addExistingPath('/usr/local/bin/goose'); + const result = checkPathExists('/usr/local/bin/goose'); + expect(result).toBe(true); + }); + + test('returns false when file does not exist', () => { + const result = checkPathExists('/nonexistent/path'); + expect(result).toBe(false); + }); + }); + + describe('findInPath', () => { + describe('on unix-like systems', () => { + const originalPlatform = process.platform; + + beforeEach(() => { + Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true }); + }); + + afterEach(() => { + Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); + }); + + test('returns first matching path entry', () => { + const env: NodeJS.ProcessEnv = { + PATH: '/usr/local/bin:/opt/homebrew/bin:/usr/bin', + }; + addExistingPath('/opt/homebrew/bin/goose'); + addExistingPath('/usr/bin/goose'); + + const result = findInPath(env); + expect(result).toBe('/opt/homebrew/bin/goose'); + }); + + test('returns undefined when not found in any PATH directory', () => { + const env: NodeJS.ProcessEnv = { + PATH: '/usr/local/bin:/opt/homebrew/bin:/usr/bin', + }; + // No paths added to mockExistingPaths + + const result = findInPath(env); + expect(result).toBeUndefined(); + }); + + test('handles empty PATH', () => { + const env: NodeJS.ProcessEnv = { PATH: '' }; + + const result = findInPath(env); + expect(result).toBeUndefined(); + }); + + test('handles undefined PATH', () => { + const env: NodeJS.ProcessEnv = {}; + + const result = findInPath(env); + expect(result).toBeUndefined(); + }); + + test('skips empty path entries', () => { + const env: NodeJS.ProcessEnv = { + PATH: '/usr/local/bin::/opt/homebrew/bin', + }; + addExistingPath('/opt/homebrew/bin/goose'); + + const result = findInPath(env); + expect(result).toBe('/opt/homebrew/bin/goose'); + }); + }); + + describe('on windows', () => { + const originalPlatform = process.platform; + + beforeEach(() => { + Object.defineProperty(process, 'platform', { value: 'win32', configurable: true }); + }); + + afterEach(() => { + Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); + }); + + test('uses Path instead of PATH when PATH not present', () => { + const env: NodeJS.ProcessEnv = { + Path: 'C:\\Windows\\System32;C:\\Program Files\\Goose', + }; + // Note: path.join uses forward slash on unix, so we account for cross-platform behavior + // The actual path that would be checked by path.join + const expectedPath = 'C:\\Program Files\\Goose/goose.exe'; + addExistingPath(expectedPath); + + const result = findInPath(env); + expect(result).toBe(expectedPath); + }); + + test('looks for goose.exe on windows', () => { + const env: NodeJS.ProcessEnv = { + PATH: 'C:\\Program Files\\Goose', + }; + // Note: path.join uses forward slash on unix, so we account for cross-platform behavior + const expectedPath = 'C:\\Program Files\\Goose/goose.exe'; + addExistingPath(expectedPath); + + const result = findInPath(env); + expect(result).toBe(expectedPath); + }); + }); + }); + + describe('findInPlatformPaths', () => { + const homeDir = '/home/testuser'; + const env: NodeJS.ProcessEnv = { + LOCALAPPDATA: 'C:\\Users\\Test\\AppData\\Local', + PROGRAMFILES: 'C:\\Program Files', + }; + + describe('darwin platform', () => { + test('searches darwin-specific paths', () => { + addExistingPath('/Applications/Goose.app/Contents/MacOS/goose'); + + const result = findInPlatformPaths('darwin', homeDir, env); + expect(result).toBe('/Applications/Goose.app/Contents/MacOS/goose'); + }); + + test('returns first found path in priority order', () => { + addExistingPath('/home/testuser/.local/bin/goose'); + addExistingPath('/usr/local/bin/goose'); + + const result = findInPlatformPaths('darwin', homeDir, env); + // ~/.local/bin comes after /Applications/Goose.app in darwin paths + // So if only these two exist, it should return the first in the list + expect(result).toBe('/home/testuser/.local/bin/goose'); + }); + + test('returns undefined when no platform paths exist', () => { + const result = findInPlatformPaths('darwin', homeDir, env); + expect(result).toBeUndefined(); + }); + }); + + describe('win32 platform', () => { + test('searches win32-specific paths with expanded variables', () => { + addExistingPath('C:\\Users\\Test\\AppData\\Local\\Goose\\goose.exe'); + + const result = findInPlatformPaths('win32', homeDir, env); + expect(result).toBe('C:\\Users\\Test\\AppData\\Local\\Goose\\goose.exe'); + }); + + test('searches PROGRAMFILES location', () => { + addExistingPath('C:\\Program Files\\Goose\\goose.exe'); + + const result = findInPlatformPaths('win32', homeDir, env); + expect(result).toBe('C:\\Program Files\\Goose\\goose.exe'); + }); + }); + + describe('linux platform', () => { + test('searches linux-specific paths', () => { + addExistingPath('/usr/local/bin/goose'); + + const result = findInPlatformPaths('linux', homeDir, env); + expect(result).toBe('/usr/local/bin/goose'); + }); + + test('expands tilde in linux paths', () => { + addExistingPath('/home/testuser/.local/bin/goose'); + + const result = findInPlatformPaths('linux', homeDir, env); + expect(result).toBe('/home/testuser/.local/bin/goose'); + }); + }); + + describe('unknown platform', () => { + test('returns undefined for unknown platform', () => { + const result = findInPlatformPaths('freebsd' as NodeJS.Platform, homeDir, env); + expect(result).toBeUndefined(); + }); + }); + }); + + describe('discoverBinary', () => { + const homeDir = '/home/testuser'; + const env: NodeJS.ProcessEnv = { + PATH: '/usr/local/bin:/usr/bin', + LOCALAPPDATA: 'C:\\Users\\Test\\AppData\\Local', + }; + + describe('precedence order', () => { + test('user configured path takes precedence over PATH', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '/custom/path/goose', + platform: 'darwin', + env, + homeDir, + }; + addExistingPath('/custom/path/goose'); + addExistingPath('/usr/local/bin/goose'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('/custom/path/goose'); + } + }); + + test('user configured path takes precedence over platform paths', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '/custom/path/goose', + platform: 'darwin', + env: { ...env, PATH: '' }, + homeDir, + }; + addExistingPath('/custom/path/goose'); + addExistingPath('/Applications/Goose.app/Contents/MacOS/goose'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('/custom/path/goose'); + } + }); + + test('PATH takes precedence over platform paths when no user config', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: undefined, + platform: 'darwin', + env, + homeDir, + }; + addExistingPath('/usr/local/bin/goose'); + addExistingPath('/Applications/Goose.app/Contents/MacOS/goose'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('/usr/local/bin/goose'); + } + }); + + test('falls back to platform paths when user config and PATH fail', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '/nonexistent/goose', + platform: 'darwin', + env: { ...env, PATH: '' }, + homeDir, + }; + addExistingPath('/Applications/Goose.app/Contents/MacOS/goose'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('/Applications/Goose.app/Contents/MacOS/goose'); + } + }); + + test('falls back to platform paths when no user config and PATH fails', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: undefined, + platform: 'darwin', + env: { ...env, PATH: '' }, + homeDir, + }; + addExistingPath('/opt/homebrew/bin/goose'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('/opt/homebrew/bin/goose'); + } + }); + }); + + describe('path expansion', () => { + test('expands tilde in user configured path', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '~/.local/bin/goose', + platform: 'darwin', + env, + homeDir, + }; + addExistingPath('/home/testuser/.local/bin/goose'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('/home/testuser/.local/bin/goose'); + } + }); + + test('expands environment variables in user configured path', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '%LOCALAPPDATA%\\Goose\\goose.exe', + platform: 'win32', + env, + homeDir, + }; + addExistingPath('C:\\Users\\Test\\AppData\\Local\\Goose\\goose.exe'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('C:\\Users\\Test\\AppData\\Local\\Goose\\goose.exe'); + } + }); + }); + + describe('BinaryNotFoundError', () => { + test('returns BinaryNotFoundError when binary not found anywhere', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: undefined, + platform: 'darwin', + env: { ...env, PATH: '' }, + homeDir, + }; + // No paths added to mockExistingPaths + + const result = discoverBinary(config); + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(result.left._tag).toBe('BinaryNotFoundError'); + } + }); + + test('returns BinaryNotFoundError when user config path does not exist', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '/nonexistent/goose', + platform: 'darwin', + env: { ...env, PATH: '' }, + homeDir, + }; + // No paths exist + + const result = discoverBinary(config); + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(result.left._tag).toBe('BinaryNotFoundError'); + } + }); + + test('BinaryNotFoundError includes searched paths', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '/custom/goose', + platform: 'darwin', + env: { ...env, PATH: '' }, + homeDir, + }; + // No paths exist + + const result = discoverBinary(config); + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(result.left._tag).toBe('BinaryNotFoundError'); + expect(result.left.searchedPaths.length).toBeGreaterThan(0); + expect(result.left.searchedPaths).toContain('/custom/goose'); + } + }); + + test('BinaryNotFoundError includes platform', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: undefined, + platform: 'linux', + env: { ...env, PATH: '' }, + homeDir, + }; + + const result = discoverBinary(config); + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(result.left._tag).toBe('BinaryNotFoundError'); + expect(result.left.platform).toBe('linux'); + } + }); + }); + + describe('edge cases', () => { + test('handles undefined user config path', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: undefined, + platform: 'darwin', + env, + homeDir, + }; + addExistingPath('/usr/local/bin/goose'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('/usr/local/bin/goose'); + } + }); + + test('handles empty environment', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: undefined, + platform: 'darwin', + env: {}, + homeDir, + }; + addExistingPath('/Applications/Goose.app/Contents/MacOS/goose'); + + const result = discoverBinary(config); + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('/Applications/Goose.app/Contents/MacOS/goose'); + } + }); + }); + }); + + describe('getAllSearchPaths', () => { + test('includes user configured path when present', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '/custom/goose', + platform: 'darwin', + env: {}, + homeDir: '/home/testuser', + }; + + const paths = getAllSearchPaths(config); + expect(paths).toContain('/custom/goose'); + }); + + test('includes PATH environment variable placeholder', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: undefined, + platform: 'darwin', + env: {}, + homeDir: '/home/testuser', + }; + + const paths = getAllSearchPaths(config); + expect(paths).toContain('PATH environment variable'); + }); + + test('includes platform-specific paths', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: undefined, + platform: 'darwin', + env: {}, + homeDir: '/home/testuser', + }; + + const paths = getAllSearchPaths(config); + expect(paths).toContain('/Applications/Goose.app/Contents/MacOS/goose'); + expect(paths).toContain('/home/testuser/.local/bin/goose'); + }); + + test('expands tilde in returned paths', () => { + const config: BinaryDiscoveryConfig = { + userConfiguredPath: '~/.local/bin/goose', + platform: 'darwin', + env: {}, + homeDir: '/home/testuser', + }; + + const paths = getAllSearchPaths(config); + expect(paths[0]).toBe('/home/testuser/.local/bin/goose'); + }); + }); +}); diff --git a/src/extension/binaryDiscovery.ts b/src/extension/binaryDiscovery.ts new file mode 100644 index 0000000..9f7d70b --- /dev/null +++ b/src/extension/binaryDiscovery.ts @@ -0,0 +1,164 @@ +/** + * Cross-platform goose binary discovery. + * Searches user settings, PATH, and platform-specific installation locations. + */ + +import * as E from 'fp-ts/Either'; +import { pipe } from 'fp-ts/function'; +import * as fs from 'fs'; +import * as path from 'path'; +import { BinaryNotFoundError, createBinaryNotFoundError, toLeft, toRight } from '../shared/errors'; +import { BinaryDiscoveryConfig } from '../shared/types'; + +// ============================================================================ +// Platform-Specific Search Paths +// ============================================================================ + +const SEARCH_PATHS: Partial> = { + darwin: [ + '/Applications/Goose.app/Contents/MacOS/goose', + '~/.local/bin/goose', + '/usr/local/bin/goose', + '/opt/homebrew/bin/goose', + ], + win32: ['%LOCALAPPDATA%\\Goose\\goose.exe', '%PROGRAMFILES%\\Goose\\goose.exe'], + linux: [ + '~/.local/bin/goose', + '/usr/local/bin/goose', + '/usr/bin/goose', + '/usr/share/goose/bin/goose', + ], +}; + +// ============================================================================ +// Path Expansion +// ============================================================================ + +/** Expand ~ and environment variables in a path */ +export function expandPath(pathStr: string, homeDir: string, env: NodeJS.ProcessEnv): string { + let expanded = pathStr; + + if (expanded.startsWith('~')) { + expanded = path.join(homeDir, expanded.slice(1)); + } + + expanded = expanded.replace(/%([^%]+)%/g, (_, varName: string) => { + return env[varName] ?? ''; + }); + + return expanded; +} + +// ============================================================================ +// Path Checking +// ============================================================================ + +/** Check if a file exists and is executable */ +export function checkPathExists(filePath: string): boolean { + try { + fs.accessSync(filePath, fs.constants.X_OK); + return true; + } catch { + return false; + } +} + +// ============================================================================ +// PATH Search +// ============================================================================ + +/** Find goose binary in PATH environment variable */ +export function findInPath(env: NodeJS.ProcessEnv): string | undefined { + const pathEnv = env.PATH ?? env.Path ?? ''; + const pathSeparator = process.platform === 'win32' ? ';' : ':'; + const executableName = process.platform === 'win32' ? 'goose.exe' : 'goose'; + + for (const dir of pathEnv.split(pathSeparator)) { + if (!dir) continue; + const fullPath = path.join(dir, executableName); + if (checkPathExists(fullPath)) { + return fullPath; + } + } + + return undefined; +} + +// ============================================================================ +// Platform Path Search +// ============================================================================ + +/** Find goose binary in platform-specific paths */ +export function findInPlatformPaths( + platform: NodeJS.Platform, + homeDir: string, + env: NodeJS.ProcessEnv +): string | undefined { + const paths = SEARCH_PATHS[platform] ?? []; + + for (const pathStr of paths) { + const expanded = expandPath(pathStr, homeDir, env); + if (checkPathExists(expanded)) { + return expanded; + } + } + + return undefined; +} + +// ============================================================================ +// Main Discovery Function +// ============================================================================ + +/** Discover the goose binary path */ +export function discoverBinary( + config: BinaryDiscoveryConfig +): E.Either { + const searchedPaths: string[] = []; + + return pipe(config.userConfiguredPath, userPath => { + if (userPath !== undefined) { + const expanded = expandPath(userPath, config.homeDir, config.env); + searchedPaths.push(expanded); + if (checkPathExists(expanded)) { + return toRight(expanded); + } + } + + const pathResult = findInPath(config.env); + if (pathResult !== undefined) { + return toRight(pathResult); + } + + const platformPaths = SEARCH_PATHS[config.platform] ?? []; + for (const pathStr of platformPaths) { + const expanded = expandPath(pathStr, config.homeDir, config.env); + searchedPaths.push(expanded); + } + + const platformResult = findInPlatformPaths(config.platform, config.homeDir, config.env); + if (platformResult !== undefined) { + return toRight(platformResult); + } + + return toLeft(createBinaryNotFoundError(searchedPaths, config.platform)); + }); +} + +/** Get all search paths that would be checked (for error messages) */ +export function getAllSearchPaths(config: BinaryDiscoveryConfig): readonly string[] { + const paths: string[] = []; + + if (config.userConfiguredPath !== undefined) { + paths.push(expandPath(config.userConfiguredPath, config.homeDir, config.env)); + } + + paths.push('PATH environment variable'); + + const platformPaths = SEARCH_PATHS[config.platform] ?? []; + for (const pathStr of platformPaths) { + paths.push(expandPath(pathStr, config.homeDir, config.env)); + } + + return paths; +} diff --git a/src/extension/commands.ts b/src/extension/commands.ts new file mode 100644 index 0000000..0ac3831 --- /dev/null +++ b/src/extension/commands.ts @@ -0,0 +1,134 @@ +/** + * VS Code command registration for Goose extension. + */ + +import * as E from 'fp-ts/Either'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { ContextChip } from '../shared/contextTypes'; +import { createAddContextChipMessage, createFocusChatInputMessage } from '../shared/messages'; +import { discoverBinary } from './binaryDiscovery'; +import { getBinaryDiscoveryConfig } from './config'; +import { Logger } from './logger'; +import { SubprocessManager } from './subprocessManager'; +import { WebviewProvider } from './webviewProvider'; + +/** Dependencies for command registration */ +export interface CommandDependencies { + readonly logger: Logger; + readonly outputChannel: vscode.OutputChannel; + readonly subprocessManager: SubprocessManager | null; + readonly getSubprocessManager: () => SubprocessManager | null; +} + +/** Register all Goose commands */ +export function registerCommands( + context: vscode.ExtensionContext, + deps: CommandDependencies +): void { + const { logger, outputChannel, getSubprocessManager } = deps; + + context.subscriptions.push( + vscode.commands.registerCommand('goose.showLogs', () => { + logger.debug('Show logs command invoked'); + outputChannel.show(); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand('goose.restart', async () => { + const manager = getSubprocessManager(); + if (!manager) { + vscode.window.showWarningMessage('Goose subprocess manager not initialized.'); + return; + } + + logger.info('Restart command invoked'); + + await manager.stop()(); + + const binaryResult = discoverBinary(getBinaryDiscoveryConfig()); + if (E.isLeft(binaryResult)) { + vscode.window.showErrorMessage('Cannot restart: Goose binary not found.'); + return; + } + + const startResult = await manager.start(binaryResult.right)(); + if (E.isLeft(startResult)) { + vscode.window.showErrorMessage('Failed to restart Goose subprocess.'); + } else { + vscode.window.showInformationMessage('Goose restarted successfully.'); + } + }) + ); + + logger.debug('Commands registered: goose.showLogs, goose.restart'); +} + +/** Generate a unique chip ID */ +function generateChipId(): string { + return `chip-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; +} + +/** Dependencies for context commands */ +export interface ContextCommandDependencies { + readonly logger: Logger; + readonly webviewProvider: WebviewProvider; + readonly getSessionManager: () => import('./sessionManager').SessionManager | null; +} + +/** Register context-related commands (selection to chat) */ +export function registerContextCommands( + context: vscode.ExtensionContext, + deps: ContextCommandDependencies +): void { + const { logger, webviewProvider, getSessionManager } = deps; + + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + 'goose.sendSelectionToChat', + async (editor: vscode.TextEditor) => { + const selection = editor.selection; + const document = editor.document; + + // Reveal the Goose panel + await vscode.commands.executeCommand('goose.chatView.focus'); + + // Ensure there's an active session + const sessionManager = getSessionManager(); + if (sessionManager && !sessionManager.getActiveSession()) { + logger.info('No active session, creating new one for context chip'); + const result = await sessionManager.createSession()(); + if (E.isLeft(result)) { + logger.error('Failed to create session for context chip:', result.left); + } + } + + // Wait for webview to be ready before sending chip + await webviewProvider.waitForReady(); + + const chip: ContextChip = { + id: generateChipId(), + filePath: document.uri.fsPath, + fileName: path.basename(document.uri.fsPath), + languageId: document.languageId, + range: selection.isEmpty + ? undefined + : { + startLine: selection.start.line + 1, + endLine: selection.end.line + 1, + }, + }; + + webviewProvider.postMessage(createAddContextChipMessage(chip)); + webviewProvider.postMessage(createFocusChatInputMessage()); + + logger.info( + `Added context chip: ${chip.fileName}${chip.range ? `:${chip.range.startLine}-${chip.range.endLine}` : ''}` + ); + } + ) + ); + + logger.debug('Context commands registered: goose.sendSelectionToChat'); +} diff --git a/src/extension/config.ts b/src/extension/config.ts new file mode 100644 index 0000000..6440832 --- /dev/null +++ b/src/extension/config.ts @@ -0,0 +1,56 @@ +/** + * VS Code configuration reader for Goose extension settings. + */ + +import { pipe } from 'fp-ts/function'; +import * as O from 'fp-ts/Option'; +import * as os from 'os'; +import * as vscode from 'vscode'; +import { BinaryDiscoveryConfig, LogLevel, parseLogLevel } from '../shared/types'; + +const CONFIG_SECTION = 'goose'; + +/** Get the configured goose binary path, or None if not set */ +export function getGooseBinaryPath(): O.Option { + const config = vscode.workspace.getConfiguration(CONFIG_SECTION); + const path = config.get('binaryPath'); + return pipe( + path, + O.fromNullable, + O.filter(p => p.length > 0) + ); +} + +/** Get the configured log level, defaulting to INFO */ +export function getLogLevel(): LogLevel { + const config = vscode.workspace.getConfiguration(CONFIG_SECTION); + const level = config.get('logLevel', 'info'); + return parseLogLevel(level); +} + +/** Get the binary discovery configuration */ +export function getBinaryDiscoveryConfig(): BinaryDiscoveryConfig { + return { + userConfiguredPath: pipe(getGooseBinaryPath(), O.toUndefined), + platform: process.platform, + env: process.env, + homeDir: os.homedir(), + }; +} + +/** Configuration change listener callback type */ +export type ConfigChangeCallback = (e: vscode.ConfigurationChangeEvent) => void; + +/** Register a listener for configuration changes */ +export function onConfigChange(callback: ConfigChangeCallback): vscode.Disposable { + return vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(CONFIG_SECTION)) { + callback(e); + } + }); +} + +/** Check if a specific setting was changed */ +export function affectsSetting(e: vscode.ConfigurationChangeEvent, setting: string): boolean { + return e.affectsConfiguration(`${CONFIG_SECTION}.${setting}`); +} diff --git a/src/extension/extension.ts b/src/extension/extension.ts new file mode 100644 index 0000000..19e622d --- /dev/null +++ b/src/extension/extension.ts @@ -0,0 +1,754 @@ +/** + * Main extension entry point for VS Code Goose Extension. + * Manages activation, subprocess lifecycle, and webview registration. + */ + +import * as E from 'fp-ts/Either'; +import * as fs from 'fs/promises'; +import * as vscode from 'vscode'; +import { + formatError, + isBinaryNotFoundError, + isSubprocessSpawnError, + SubprocessSpawnError, +} from '../shared/errors'; +import { + ContextChipData, + createChatHistoryMessage, + createErrorMessage, + createGenerationCancelledMessage, + createGenerationCompleteMessage, + createHistoryCompleteMessage, + createHistoryMessage, + createSearchResultsMessage, + createSessionCreatedMessage, + createSessionLoadedMessage, + createSessionsListMessage, + createStreamTokenMessage, + isCreateSessionMessage, + isFileSearchMessage, + isGetSessionsMessage, + isOpenExternalLinkMessage, + isSelectSessionMessage, + isSendMessageMessage, + isStopGenerationMessage, +} from '../shared/messages'; +import { DEFAULT_CAPABILITIES, SessionEntry } from '../shared/sessionTypes'; +import { JsonRpcNotification, ProcessStatus } from '../shared/types'; +import { discoverBinary } from './binaryDiscovery'; +import { registerCommands, registerContextCommands } from './commands'; +import { affectsSetting, getBinaryDiscoveryConfig, getLogLevel, onConfigChange } from './config'; +import { createFileSearchService, FileSearchService } from './fileSearchService'; +import { JsonRpcClient } from './jsonRpcClient'; +import { createLogger, Logger } from './logger'; +import { createSessionManager, SessionManager } from './sessionManager'; +import { createSessionStorage, SessionStorage } from './sessionStorage'; +import { createSubprocessManager, SubprocessManager } from './subprocessManager'; +import { checkVersion, MINIMUM_VERSION } from './versionChecker'; +import { createWebviewProvider, WebviewProvider } from './webviewProvider'; + +let logger: Logger | null = null; +let subprocessManager: SubprocessManager | null = null; +let webviewProvider: WebviewProvider | null = null; +let sessionStorage: SessionStorage | null = null; +let sessionManager: SessionManager | null = null; +let fileSearchService: FileSearchService | null = null; + +// Mock streaming state +let mockStreamingTimer: ReturnType | null = null; +let mockStreamingCancelled = false; + +const MOCK_RESPONSES = [ + "I'd be happy to help you with that! Let me think about this...\n\nHere's what I suggest:\n\n1. **First step**: Start by understanding the problem\n2. **Second step**: Break it down into smaller parts\n3. **Third step**: Implement the solution\n\n```typescript\nfunction example() {\n console.log('Hello, world!');\n}\n```\n\nLet me know if you need more details!", + "Great question! Here's a quick overview:\n\n- This is a **key concept** to understand\n- It works by processing data incrementally\n- The result is then returned to the caller\n\n> Note: This is just a mock response for testing the UI.\n\nWould you like me to elaborate on any part?", + "Sure thing! Let me explain...\n\nThe main idea here is to keep things simple and focused. Here's an example:\n\n```python\ndef greet(name):\n return f'Hello, {name}!'\n```\n\nThis demonstrates the basic pattern. The streaming UI should show this text appearing gradually, token by token.", +]; + +function setupMockStreaming(provider: WebviewProvider, log: Logger): void { + let currentResponseId: string | null = null; + + provider.onMessage(message => { + if (isSendMessageMessage(message)) { + const { content, responseId } = message.payload; + log.info(`[Mock] Received message: ${content.substring(0, 50)}...`); + + // Use the responseId provided by the webview + currentResponseId = responseId; + mockStreamingCancelled = false; + + // Pick a random mock response + const responseText = MOCK_RESPONSES[Math.floor(Math.random() * MOCK_RESPONSES.length)]; + const tokens = responseText.split(/(?<=\s)|(?=\s)/); // Split on whitespace boundaries + + let tokenIndex = 0; + + const streamNextToken = (): void => { + if (mockStreamingCancelled || tokenIndex >= tokens.length) { + if (!mockStreamingCancelled && currentResponseId) { + provider.postMessage(createGenerationCompleteMessage(currentResponseId)); + log.info('[Mock] Generation complete'); + } + mockStreamingTimer = null; + currentResponseId = null; + return; + } + + if (currentResponseId) { + provider.postMessage( + createStreamTokenMessage(currentResponseId, tokens[tokenIndex], false) + ); + } + tokenIndex++; + + // Random delay between 20-80ms for realistic streaming feel + const delay = 20 + Math.random() * 60; + mockStreamingTimer = setTimeout(streamNextToken, delay); + }; + + // Start streaming after a small initial delay + mockStreamingTimer = setTimeout(streamNextToken, 100); + } + + if (isStopGenerationMessage(message)) { + log.info('[Mock] Stop generation requested'); + mockStreamingCancelled = true; + + if (mockStreamingTimer) { + clearTimeout(mockStreamingTimer); + mockStreamingTimer = null; + } + + if (currentResponseId) { + provider.postMessage(createGenerationCancelledMessage(currentResponseId)); + currentResponseId = null; + } + } + }); + + log.info('[Mock] Mock streaming handler registered'); +} + +/** ACP streaming content block types */ +type AcpStreamContentBlock = + | { readonly type: 'text'; readonly text: string } + | { + readonly type: 'resource_link'; + readonly uri: string; + readonly name: string; + readonly mimeType?: string; + } + | { + readonly type: 'resource'; + readonly resource: { + readonly uri: string; + readonly text?: string; + readonly blob?: string; + readonly mimeType?: string; + }; + }; + +interface AcpSessionUpdateParams { + readonly sessionId: string; + readonly update: { + readonly sessionUpdate: string; + readonly content?: AcpStreamContentBlock; + }; +} + +interface AcpPromptResponse { + readonly stopReason: 'end_turn' | 'max_tokens' | 'max_turn_requests' | 'refusal' | 'cancelled'; +} + +interface AcpInitializeResponse { + readonly protocolVersion: number; + readonly agentCapabilities?: { + readonly loadSession?: boolean; + readonly promptCapabilities?: { + readonly audio?: boolean; + readonly image?: boolean; + readonly embeddedContext?: boolean; + }; + }; + readonly agentInfo?: { + readonly name?: string; + readonly version?: string; + }; +} + +/** ACP prompt content block types */ +type AcpContentBlock = + | { type: 'text'; text: string } + | { type: 'resource_link'; uri: string; name: string; mimeType?: string }; + +/** Get MIME type from file path */ +function getMimeType(filePath: string): string { + const ext = filePath.split('.').pop()?.toLowerCase() ?? ''; + const mimeTypes: Record = { + ts: 'text/typescript', + tsx: 'text/typescript', + js: 'text/javascript', + jsx: 'text/javascript', + json: 'application/json', + md: 'text/markdown', + py: 'text/x-python', + rs: 'text/x-rust', + go: 'text/x-go', + java: 'text/x-java', + c: 'text/x-c', + cpp: 'text/x-c++', + h: 'text/x-c', + css: 'text/css', + html: 'text/html', + xml: 'text/xml', + yaml: 'text/yaml', + yml: 'text/yaml', + sh: 'text/x-shellscript', + sql: 'text/x-sql', + }; + return mimeTypes[ext] ?? 'text/plain'; +} + +/** Read specific lines from a file */ +async function readFileLines( + filePath: string, + startLine: number, + endLine: number +): Promise { + const content = await fs.readFile(filePath, 'utf-8'); + const lines = content.split('\n'); + // Lines are 1-indexed in UI, convert to 0-indexed + const start = Math.max(0, startLine - 1); + const end = Math.min(lines.length, endLine); + return lines.slice(start, end).join('\n'); +} + +/** Build prompt content blocks from message and context chips */ +async function buildPromptBlocks( + content: string, + chips: readonly ContextChipData[] | undefined, + log: Logger +): Promise { + const blocks: AcpContentBlock[] = []; + + if (chips && chips.length > 0) { + for (const chip of chips) { + const fileName = chip.filePath.split('/').pop() ?? chip.filePath; + + if (chip.range) { + // Line range selection: read and send the specific lines as text + try { + const selectedContent = await readFileLines( + chip.filePath, + chip.range.startLine, + chip.range.endLine + ); + const header = `${chip.filePath}:${chip.range.startLine}-${chip.range.endLine}`; + blocks.push({ + type: 'text', + text: `File: ${header}\n\`\`\`\n${selectedContent}\n\`\`\``, + }); + log.debug(`Added selected content from ${header}`); + } catch (err) { + log.warn(`Failed to read lines from ${chip.filePath}:`, err); + } + } else { + // Whole file: use resource_link (Goose reads it) + blocks.push({ + type: 'resource_link', + uri: `file://${chip.filePath}`, + name: fileName, + mimeType: getMimeType(chip.filePath), + }); + log.debug(`Added resource link: file://${chip.filePath}`); + } + } + } + + // Add user message text + if (content) { + blocks.push({ type: 'text', text: content }); + } + + return blocks; +} + +async function initializeAcpSession( + client: JsonRpcClient, + workingDirectory: string, + manager: SessionManager, + log: Logger +): Promise { + log.info('Initializing ACP connection...'); + + // Call ACP initialize to get agent capabilities + const initResult = await client.request('initialize', { + protocolVersion: 1, + clientInfo: { + name: 'vscode-goose', + version: '0.1.0', + }, + clientCapabilities: { + fs: { readTextFile: false, writeTextFile: false }, + terminal: false, + }, + })(); + + let capabilities = DEFAULT_CAPABILITIES; + + if (E.isRight(initResult)) { + const response = initResult.right; + log.info(`ACP initialized with protocol version: ${response.protocolVersion}`); + + if (response.agentInfo) { + log.info(`Agent: ${response.agentInfo.name} v${response.agentInfo.version}`); + } + + // Parse capabilities from response + const agentCaps = response.agentCapabilities; + if (agentCaps) { + capabilities = { + loadSession: agentCaps.loadSession ?? DEFAULT_CAPABILITIES.loadSession, + promptCapabilities: { + image: agentCaps.promptCapabilities?.image ?? false, + audio: agentCaps.promptCapabilities?.audio ?? false, + embeddedContext: agentCaps.promptCapabilities?.embeddedContext ?? false, + }, + }; + log.info( + `Agent capabilities: loadSession=${capabilities.loadSession}, embeddedContext=${capabilities.promptCapabilities.embeddedContext}` + ); + } + } else { + log.warn('ACP initialize failed, using default capabilities:', initResult.left); + } + + manager.initialize(client, capabilities, workingDirectory); + + const existingSession = manager.getActiveSession(); + if (existingSession) { + log.info(`Found existing session: ${existingSession.sessionId}, attempting to restore...`); + + // Try to restore the session with the server + if (manager.hasLoadSessionCapability()) { + const loadResult = await manager.loadSession(existingSession.sessionId)(); + if (E.isRight(loadResult)) { + log.info(`Successfully restored session: ${existingSession.sessionId}`); + return existingSession; + } + log.warn(`Failed to restore session ${existingSession.sessionId}, creating new session`); + } else { + // Server doesn't support session/load - create a fresh session + log.info('Server does not support session/load, creating new session'); + } + } + + const result = await manager.createSession()(); + + if (E.isLeft(result)) { + log.error('Failed to create ACP session:', result.left); + return null; + } + + log.info(`ACP session created: ${result.right.sessionId}`); + return result.right; +} + +function setupAcpCommunication( + provider: WebviewProvider, + client: JsonRpcClient, + manager: SessionManager, + log: Logger +): void { + let currentResponseId: string | null = null; + + const getActiveSessionId = (): string | null => { + return manager.getActiveSessionId(); + }; + + client.onNotification((notification: JsonRpcNotification) => { + const method = notification.method; + const params = notification.params as AcpSessionUpdateParams | undefined; + + if (method === 'session/update' && params?.update) { + const { sessionUpdate, content } = params.update; + + if ( + sessionUpdate === 'agent_message_chunk' && + currentResponseId && + content?.type === 'text' + ) { + provider.postMessage(createStreamTokenMessage(currentResponseId, content.text, false)); + } + } + }); + + manager.onHistoryMessage(message => { + provider.postMessage(createHistoryMessage(message)); + }); + + manager.onHistoryComplete((sessionId, messageCount) => { + provider.postMessage(createHistoryCompleteMessage(sessionId, messageCount)); + }); + + provider.onMessage(message => { + if (isSendMessageMessage(message)) { + const { content, responseId, contextChips } = message.payload; + currentResponseId = responseId; + const activeSessionId = getActiveSessionId(); + + if (!activeSessionId) { + log.error('No active session'); + provider.postMessage( + createErrorMessage('No Active Session', 'Please create or select a session first.') + ); + return; + } + + log.info(`Sending message to ACP: ${content.substring(0, 50)}...`); + if (contextChips && contextChips.length > 0) { + log.info(`With ${contextChips.length} context chip(s)`); + } + + const activeSession = manager.getActiveSession(); + if (activeSession && activeSession.title === 'New Session') { + manager.updateSessionTitle(activeSession.sessionId, content); + } + + const sendRequest = async (): Promise => { + // Build prompt content blocks with resource links for context + const promptBlocks = await buildPromptBlocks(content, contextChips, log); + + const result = await client.request('session/prompt', { + sessionId: activeSessionId, + prompt: promptBlocks, + })(); + + if (E.isLeft(result)) { + log.error('ACP request failed:', result.left); + provider.postMessage( + createErrorMessage( + 'Message Send Failed', + `Failed to send message: ${result.left.message}`, + { label: 'View Logs', command: 'goose.showLogs' } + ) + ); + if (currentResponseId) { + provider.postMessage(createGenerationCancelledMessage(currentResponseId)); + currentResponseId = null; + } + } else { + log.info(`Generation completed with stopReason: ${result.right.stopReason}`); + if (currentResponseId) { + if (result.right.stopReason === 'cancelled') { + provider.postMessage(createGenerationCancelledMessage(currentResponseId)); + } else { + provider.postMessage(createGenerationCompleteMessage(currentResponseId)); + } + currentResponseId = null; + } + } + }; + + sendRequest(); + } + + if (isStopGenerationMessage(message)) { + const activeSessionId = getActiveSessionId(); + if (activeSessionId) { + log.info('Sending cancel request to ACP'); + const cancelResult = client.notify('session/cancel', { sessionId: activeSessionId }); + if (E.isLeft(cancelResult)) { + log.error('Failed to send cancel notification:', cancelResult.left); + } + } + } + + if (isCreateSessionMessage(message)) { + log.info('Creating new session...'); + manager + .createSession()() + .then(result => { + if (E.isRight(result)) { + provider.postMessage(createSessionCreatedMessage(result.right)); + provider.postMessage( + createSessionsListMessage(manager.getSessions(), result.right.sessionId) + ); + log.info(`New session created: ${result.right.sessionId}`); + } else { + log.error('Failed to create session:', result.left); + provider.postMessage( + createErrorMessage('Session Creation Failed', result.left.message) + ); + } + }); + } + + if (isGetSessionsMessage(message)) { + log.debug('Sending session list'); + provider.postMessage( + createSessionsListMessage(manager.getSessions(), manager.getActiveSessionId()) + ); + } + + if (isSelectSessionMessage(message)) { + const { sessionId } = message.payload; + log.info(`Switching to session: ${sessionId}`); + + // Clear chat before loading history + provider.postMessage(createChatHistoryMessage([])); + + manager + .loadSession(sessionId)() + .then(result => { + if (E.isRight(result)) { + provider.postMessage( + createSessionLoadedMessage(sessionId, !manager.hasLoadSessionCapability()) + ); + provider.postMessage(createSessionsListMessage(manager.getSessions(), sessionId)); + log.info(`Session loaded: ${sessionId}`); + } else { + log.error('Failed to load session:', result.left); + provider.postMessage(createErrorMessage('Session Load Failed', result.left.message)); + } + }); + } + }); + + log.info('ACP communication handler registered'); +} + +function getWorkspaceFolder(): string { + const folders = vscode.workspace.workspaceFolders; + if (folders && folders.length > 0) { + return folders[0].uri.fsPath; + } + return process.cwd(); +} + +function setupExternalLinkHandler(provider: WebviewProvider, log: Logger): void { + provider.onMessage(message => { + if (isOpenExternalLinkMessage(message)) { + const { url } = message.payload; + log.info(`Opening external link: ${url}`); + vscode.env.openExternal(vscode.Uri.parse(url)); + } + }); + log.debug('External link handler registered'); +} + +function setupFileSearchHandler( + provider: WebviewProvider, + searchService: FileSearchService, + log: Logger +): void { + provider.onMessage(async message => { + if (isFileSearchMessage(message)) { + const { query } = message.payload; + log.debug(`File search request: "${query}"`); + + try { + const results = await searchService.search(query); + provider.postMessage(createSearchResultsMessage(results)); + log.debug(`File search returned ${results.length} results`); + } catch (error) { + log.error('File search failed:', error); + provider.postMessage(createSearchResultsMessage([])); + } + } + }); + log.debug('File search handler registered'); +} + +function showSubprocessError(error: SubprocessSpawnError): void { + vscode.window + .showErrorMessage( + `Failed to start Goose: ${error.code}. Check the Goose output for details.`, + 'View Logs' + ) + .then(selection => { + if (selection === 'View Logs') { + vscode.commands.executeCommand('goose.showLogs'); + } + }); +} + +export async function activate(context: vscode.ExtensionContext): Promise { + const outputChannel = vscode.window.createOutputChannel('Goose'); + logger = createLogger(outputChannel, getLogLevel()); + + logger.info('Goose extension activating...'); + + sessionStorage = createSessionStorage(context.globalState); + sessionManager = createSessionManager(sessionStorage, logger.child('Session')); + + webviewProvider = createWebviewProvider({ + extensionUri: context.extensionUri, + logger: logger.child('Webview'), + }); + + context.subscriptions.push( + vscode.window.registerWebviewViewProvider('goose.chatView', webviewProvider, { + webviewOptions: { retainContextWhenHidden: true }, + }) + ); + + registerCommands(context, { + logger, + outputChannel, + subprocessManager, + getSubprocessManager: () => subprocessManager, + }); + + registerContextCommands(context, { + logger: logger.child('Context'), + webviewProvider, + getSessionManager: () => sessionManager, + }); + + setupExternalLinkHandler(webviewProvider, logger.child('Links')); + + fileSearchService = createFileSearchService(logger.child('FileSearch')); + setupFileSearchHandler(webviewProvider, fileSearchService, logger.child('FileSearch')); + + const binaryResult = discoverBinary(getBinaryDiscoveryConfig()); + + if (E.isLeft(binaryResult)) { + const error = binaryResult.left; + logger.error('Binary discovery failed:', formatError(error)); + + if (isBinaryNotFoundError(error)) { + // Send version status to webview for in-panel messaging + webviewProvider.updateVersionStatus({ + status: 'blocked_missing', + minimumVersion: MINIMUM_VERSION, + installUrl: 'https://block.github.io/goose/docs/quickstart', + }); + } + + logger.info('Goose extension activated (binary not found - version check blocked).'); + return; + } + + const binaryPath = binaryResult.right; + logger.info(`Found goose binary at: ${binaryPath}`); + + // Version check before spawning subprocess + const versionResult = await checkVersion(binaryPath)(); + + if (E.isLeft(versionResult)) { + const error = versionResult.left; + logger.error( + `Goose version check failed: detected ${error.detectedVersion}, requires ${error.minimumVersion}` + ); + + webviewProvider.updateVersionStatus({ + status: 'blocked_outdated', + detectedVersion: error.detectedVersion, + minimumVersion: error.minimumVersion, + updateUrl: error.updateUrl, + }); + + logger.info('Goose extension activated (version incompatible - version check blocked).'); + return; + } + + logger.info( + `Goose version ${versionResult.right.version} detected (meets minimum ${MINIMUM_VERSION})` + ); + + subprocessManager = createSubprocessManager({ + logger: logger.child('Subprocess'), + workingDirectory: getWorkspaceFolder(), + }); + + subprocessManager.onStatusChange(status => { + logger?.info(`Subprocess status: ${status}`); + webviewProvider?.updateStatus(status); + + if (status === ProcessStatus.ERROR) { + vscode.window.showWarningMessage( + 'Goose subprocess crashed. Use "Goose: Restart" to reconnect.' + ); + } + }); + + const startResult = await subprocessManager.start(binaryPath)(); + + if (E.isLeft(startResult)) { + const error = startResult.left; + logger.error('Failed to start subprocess:', formatError(error)); + + if (isSubprocessSpawnError(error)) { + showSubprocessError(error); + } + + setupMockStreaming(webviewProvider, logger.child('Mock')); + logger.info('[Mock] Mock streaming enabled (subprocess failed to start)'); + } else { + logger.info('Subprocess started successfully'); + webviewProvider.updateStatus(ProcessStatus.RUNNING); + + const clientResult = subprocessManager.getClient(); + if (E.isRight(clientResult)) { + const client = clientResult.right; + const session = await initializeAcpSession( + client, + getWorkspaceFolder(), + sessionManager, + logger.child('ACP') + ); + + if (session) { + setupAcpCommunication(webviewProvider, client, sessionManager, logger.child('ACP')); + logger.info('ACP communication enabled'); + } else { + setupMockStreaming(webviewProvider, logger.child('Mock')); + logger.info('[Mock] Mock streaming enabled (session initialization failed)'); + } + } else { + setupMockStreaming(webviewProvider, logger.child('Mock')); + logger.info('[Mock] Mock streaming enabled (no client available)'); + } + } + + registerConfigChangeHandler(context); + + logger.info('Goose extension activated.'); +} + +function registerConfigChangeHandler(context: vscode.ExtensionContext): void { + context.subscriptions.push( + onConfigChange(e => { + if (affectsSetting(e, 'logLevel')) { + logger?.setLevel(getLogLevel()); + logger?.info('Log level updated'); + } + if (affectsSetting(e, 'binaryPath')) { + logger?.info('Binary path setting changed - use "Goose: Restart" to apply'); + } + }) + ); +} + +export async function deactivate(): Promise { + logger?.info('Goose extension deactivating...'); + + if (sessionManager) { + sessionManager.dispose(); + sessionManager = null; + } + + if (fileSearchService) { + fileSearchService.dispose(); + fileSearchService = null; + } + + if (subprocessManager) { + await subprocessManager.stop()(); + subprocessManager = null; + } + + sessionStorage = null; + + logger?.info('Goose extension deactivated.'); +} diff --git a/src/extension/fileSearchService.ts b/src/extension/fileSearchService.ts new file mode 100644 index 0000000..f9d43e3 --- /dev/null +++ b/src/extension/fileSearchService.ts @@ -0,0 +1,155 @@ +/** + * File search service for @ file picker. + * Uses VS Code workspace APIs to find files with fuzzy matching. + */ + +import * as path from 'path'; +import * as vscode from 'vscode'; +import { FileSearchResult } from '../shared/contextTypes'; +import { Logger } from './logger'; + +/** Service for searching workspace files */ +export interface FileSearchService { + readonly search: (query: string) => Promise; + readonly dispose: () => void; +} + +/** Map file extension to VS Code language identifier */ +function getLanguageId(filePath: string): string { + const ext = path.extname(filePath).toLowerCase(); + const extensionMap: Record = { + '.ts': 'typescript', + '.tsx': 'typescriptreact', + '.js': 'javascript', + '.jsx': 'javascriptreact', + '.py': 'python', + '.rs': 'rust', + '.go': 'go', + '.java': 'java', + '.cs': 'csharp', + '.cpp': 'cpp', + '.cc': 'cpp', + '.cxx': 'cpp', + '.c': 'c', + '.h': 'c', + '.hpp': 'cpp', + '.html': 'html', + '.htm': 'html', + '.css': 'css', + '.scss': 'scss', + '.sass': 'scss', + '.less': 'less', + '.json': 'json', + '.md': 'markdown', + '.yaml': 'yaml', + '.yml': 'yaml', + '.xml': 'xml', + '.sql': 'sql', + '.sh': 'shell', + '.bash': 'bash', + '.zsh': 'shell', + '.ps1': 'powershell', + '.rb': 'ruby', + '.php': 'php', + '.swift': 'swift', + '.kt': 'kotlin', + '.kts': 'kotlin', + '.scala': 'scala', + '.vue': 'vue', + '.svelte': 'svelte', + }; + + return extensionMap[ext] ?? 'plaintext'; +} + +/** + * Create a file search service for workspace file discovery. + * Tracks recently opened files for sorting results. + */ +export function createFileSearchService(logger: Logger): FileSearchService { + const recentFiles: Map = new Map(); + + // Track recently opened files + const disposable = vscode.workspace.onDidOpenTextDocument(doc => { + // Only track workspace files (not untitled, git, etc.) + if (doc.uri.scheme === 'file') { + recentFiles.set(doc.uri.fsPath, Date.now()); + } + }); + + async function search(query: string): Promise { + const exclude = + '{**/node_modules/**,**/.git/**,**/dist/**,**/build/**,**/.venv/**,**/venv/**,**/__pycache__/**,**/target/**}'; + const lowerQuery = query.toLowerCase(); + + logger.debug(`Searching files with query: ${query}`); + + let uris: vscode.Uri[]; + + if (query) { + // When there's a query, use glob pattern to let VS Code search more efficiently + // Try multiple patterns to catch different cases + const patterns = [ + `**/*${query}*`, // Files containing query anywhere in name + `**/*${query}*/**`, // Directories containing query + ]; + + const allUris: vscode.Uri[] = []; + for (const pattern of patterns) { + const found = await vscode.workspace.findFiles(pattern, exclude, 500); + allUris.push(...found); + } + + // Deduplicate by path + const seen = new Set(); + uris = allUris.filter(uri => { + if (seen.has(uri.fsPath)) return false; + seen.add(uri.fsPath); + return true; + }); + } else { + // No query: fetch a large sample of files for initial display + // Use a high limit to ensure good coverage of subdirectories + uris = await vscode.workspace.findFiles('**/*', exclude, 10000); + } + + let results: FileSearchResult[] = uris.map(uri => { + const fileName = path.basename(uri.fsPath); + const relativePath = vscode.workspace.asRelativePath(uri, false); + // Get directory part of relative path (exclude filename) + const relativeDir = path.dirname(relativePath); + + return { + path: uri.fsPath, + fileName, + relativePath: relativeDir === '.' ? '' : relativeDir, + languageId: getLanguageId(uri.fsPath), + recentScore: recentFiles.get(uri.fsPath) ?? 0, + }; + }); + + // Additional case-insensitive filtering when query is provided + // (glob patterns may be case-sensitive on some platforms) + if (query) { + results = results.filter(r => r.fileName.toLowerCase().includes(lowerQuery)); + } + + // Sort by recent score descending (most recently opened first) + results.sort((a, b) => b.recentScore - a.recentScore); + + // Limit to 20 results + results = results.slice(0, 20); + + logger.debug(`Found ${results.length} files for query: ${query}`); + + return results; + } + + function dispose(): void { + disposable.dispose(); + recentFiles.clear(); + logger.debug('FileSearchService disposed'); + } + + return { search, dispose }; +} diff --git a/src/extension/jsonRpcClient.test.ts b/src/extension/jsonRpcClient.test.ts new file mode 100644 index 0000000..af53100 --- /dev/null +++ b/src/extension/jsonRpcClient.test.ts @@ -0,0 +1,548 @@ +import { beforeEach, describe, expect, test } from 'bun:test'; +import * as E from 'fp-ts/Either'; +import { isJsonRpcError, isJsonRpcTimeoutError } from '../shared/errors'; +import { createMockStreams, MockStreams } from '../test/mocks/streams'; +import { createJsonRpcClient, JsonRpcClient, JsonRpcClientConfig } from './jsonRpcClient'; +import { Logger } from './logger'; + +/** + * Tests for JSON-RPC 2.0 client implementation. + * Uses mock streams to simulate subprocess communication. + */ + +// Create a no-op logger for testing +function createMockLogger(): Logger { + const noop = () => undefined; + return { + debug: noop, + info: noop, + warn: noop, + error: noop, + }; +} + +describe('createJsonRpcClient', () => { + let mockStreams: MockStreams; + let client: JsonRpcClient; + let logger: Logger; + + beforeEach(() => { + mockStreams = createMockStreams(); + logger = createMockLogger(); + const config: JsonRpcClientConfig = { + stdin: mockStreams.stdin, + stdout: mockStreams.stdout, + logger, + timeoutMs: 1000, // Short timeout for tests + }; + client = createJsonRpcClient(config); + }); + + describe('request()', () => { + test('encodes valid JSON-RPC 2.0 format', async () => { + const requestTask = client.request('test.method', { key: 'value' }); + + // Start the request + const requestPromise = requestTask(); + + // Wait a tick for the write to complete + await new Promise(resolve => setTimeout(resolve, 0)); + + // Check what was written + expect(mockStreams.written.length).toBe(1); + const sentMessage = JSON.parse(mockStreams.written[0].trim()); + expect(sentMessage.jsonrpc).toBe('2.0'); + expect(sentMessage.method).toBe('test.method'); + expect(sentMessage.params).toEqual({ key: 'value' }); + expect(typeof sentMessage.id).toBe('number'); + expect(sentMessage.id).toBe(1); + + // Send response to complete the request + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 1, result: 'ok' })); + await requestPromise; + }); + + test('auto-increments request ID', async () => { + // Make two requests + const request1 = client.request('method1'); + const request2 = client.request('method2'); + + // Start both requests + const promise1 = request1(); + const promise2 = request2(); + + // Wait for writes + await new Promise(resolve => setTimeout(resolve, 0)); + + expect(mockStreams.written.length).toBe(2); + const msg1 = JSON.parse(mockStreams.written[0].trim()); + const msg2 = JSON.parse(mockStreams.written[1].trim()); + + expect(msg1.id).toBe(1); + expect(msg2.id).toBe(2); + + // Resolve requests + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 1, result: 'result1' })); + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 2, result: 'result2' })); + + await promise1; + await promise2; + }); + + test('omits params when undefined', async () => { + const requestTask = client.request('no.params'); + const requestPromise = requestTask(); + + await new Promise(resolve => setTimeout(resolve, 0)); + + const sentMessage = JSON.parse(mockStreams.written[0].trim()); + expect(sentMessage.params).toBeUndefined(); + expect('params' in sentMessage).toBe(false); + + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 1, result: null })); + await requestPromise; + }); + + test('resolves with result on success response', async () => { + const requestTask = client.request<{ data: string }>('get.data'); + const requestPromise = requestTask(); + + // Wait for write + await new Promise(resolve => setTimeout(resolve, 0)); + + // Send success response + const expectedResult = { data: 'hello world' }; + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 1, result: expectedResult })); + + const result = await requestPromise; + + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toEqual(expectedResult); + } + }); + + test('rejects with JsonRpcError on error response', async () => { + const requestTask = client.request('failing.method'); + const requestPromise = requestTask(); + + await new Promise(resolve => setTimeout(resolve, 0)); + + // Send error response + mockStreams.pushResponse( + JSON.stringify({ + jsonrpc: '2.0', + id: 1, + error: { + code: -32601, + message: 'Method not found', + data: { details: 'unknown method' }, + }, + }) + ); + + const result = await requestPromise; + + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(isJsonRpcError(result.left)).toBe(true); + expect(result.left._tag).toBe('JsonRpcError'); + expect(result.left.code).toBe(-32601); + expect(result.left.message).toBe('Method not found'); + expect(result.left.data).toEqual({ details: 'unknown method' }); + } + }); + + test('rejects with JsonRpcTimeoutError on timeout', async () => { + // Create client with very short timeout + const shortTimeoutConfig: JsonRpcClientConfig = { + stdin: mockStreams.stdin, + stdout: mockStreams.stdout, + logger, + timeoutMs: 50, // 50ms timeout + }; + const shortTimeoutClient = createJsonRpcClient(shortTimeoutConfig); + + const requestTask = shortTimeoutClient.request('slow.method'); + const result = await requestTask(); + + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(isJsonRpcTimeoutError(result.left)).toBe(true); + expect(result.left._tag).toBe('JsonRpcTimeoutError'); + expect(result.left.method).toBe('slow.method'); + expect(result.left.timeoutMs).toBe(50); + expect(result.left.requestId).toBe(1); + } + }); + + test('matches responses to correct pending requests', async () => { + const request1 = client.request('method1'); + const request2 = client.request('method2'); + + const promise1 = request1(); + const promise2 = request2(); + + await new Promise(resolve => setTimeout(resolve, 0)); + + // Send responses out of order + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 2, result: 'result2' })); + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 1, result: 'result1' })); + + const [result1, result2] = await Promise.all([promise1, promise2]); + + expect(E.isRight(result1) && result1.right).toBe('result1'); + expect(E.isRight(result2) && result2.right).toBe('result2'); + }); + }); + + describe('notify()', () => { + test('encodes notification without ID field', () => { + const result = client.notify('notification.method', { key: 'value' }); + + expect(E.isRight(result)).toBe(true); + expect(mockStreams.written.length).toBe(1); + + const sentMessage = JSON.parse(mockStreams.written[0].trim()); + expect(sentMessage.jsonrpc).toBe('2.0'); + expect(sentMessage.method).toBe('notification.method'); + expect(sentMessage.params).toEqual({ key: 'value' }); + expect('id' in sentMessage).toBe(false); + }); + + test('omits params when undefined', () => { + client.notify('simple.notification'); + + const sentMessage = JSON.parse(mockStreams.written[0].trim()); + expect('params' in sentMessage).toBe(false); + }); + + test('returns Right on success', () => { + const result = client.notify('test.notification'); + + expect(E.isRight(result)).toBe(true); + }); + + test('returns Left when client is disposed', () => { + client.dispose(); + + const result = client.notify('after.dispose'); + + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(result.left._tag).toBe('JsonRpcParseError'); + expect(result.left.parseError).toBe('Client disposed'); + } + }); + }); + + describe('onNotification()', () => { + test('invokes callback for incoming notifications', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Push a notification (no ID field) + mockStreams.pushResponse( + JSON.stringify({ + jsonrpc: '2.0', + method: 'server.event', + params: { event: 'something_happened' }, + }) + ); + + // Wait for event processing + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(receivedNotifications.length).toBe(1); + expect(receivedNotifications[0]).toEqual({ + jsonrpc: '2.0', + method: 'server.event', + params: { event: 'something_happened' }, + }); + }); + + test('invokes multiple callbacks for same notification', async () => { + let callback1Called = false; + let callback2Called = false; + + client.onNotification(() => { + callback1Called = true; + }); + client.onNotification(() => { + callback2Called = true; + }); + + mockStreams.pushResponse( + JSON.stringify({ + jsonrpc: '2.0', + method: 'broadcast', + }) + ); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(callback1Called).toBe(true); + expect(callback2Called).toBe(true); + }); + + test('does not invoke callback for response messages', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Start a request + const requestPromise = client.request('test')(); + await new Promise(resolve => setTimeout(resolve, 0)); + + // Push a response (has ID field) + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 1, result: 'ok' })); + + await requestPromise; + await new Promise(resolve => setTimeout(resolve, 10)); + + // Notification callback should not be invoked for responses + expect(receivedNotifications.length).toBe(0); + }); + + test('handles notification without params', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + mockStreams.pushResponse( + JSON.stringify({ + jsonrpc: '2.0', + method: 'ping', + }) + ); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(receivedNotifications.length).toBe(1); + expect(receivedNotifications[0]).toEqual({ + jsonrpc: '2.0', + method: 'ping', + }); + }); + + test('callback errors are caught and do not prevent other callbacks', async () => { + let callback2Called = false; + + client.onNotification(() => { + throw new Error('Callback error'); + }); + client.onNotification(() => { + callback2Called = true; + }); + + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', method: 'event' })); + + await new Promise(resolve => setTimeout(resolve, 10)); + + // Second callback should still be called despite first throwing + expect(callback2Called).toBe(true); + }); + }); + + describe('dispose()', () => { + test('rejects pending requests with JsonRpcError', async () => { + // Start a request that won't be answered + const requestPromise = client.request('pending.method')(); + + await new Promise(resolve => setTimeout(resolve, 0)); + + // Dispose the client + client.dispose(); + + const result = await requestPromise; + + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(isJsonRpcError(result.left)).toBe(true); + expect(result.left.message).toBe('Client disposed'); + } + }); + + test('rejects all pending requests when multiple are outstanding', async () => { + const promise1 = client.request('method1')(); + const promise2 = client.request('method2')(); + const promise3 = client.request('method3')(); + + await new Promise(resolve => setTimeout(resolve, 0)); + + client.dispose(); + + const [result1, result2, result3] = await Promise.all([promise1, promise2, promise3]); + + expect(E.isLeft(result1)).toBe(true); + expect(E.isLeft(result2)).toBe(true); + expect(E.isLeft(result3)).toBe(true); + }); + + test('prevents new requests after disposal', async () => { + client.dispose(); + + const result = await client.request('after.dispose')(); + + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(isJsonRpcError(result.left)).toBe(true); + expect(result.left.message).toBe('Client disposed'); + } + }); + + test('prevents new notifications after disposal', () => { + client.dispose(); + + const result = client.notify('after.dispose'); + + expect(E.isLeft(result)).toBe(true); + }); + + test('stops receiving notifications after disposal', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + client.dispose(); + + // Push notification after disposal + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', method: 'late.event' })); + + await new Promise(resolve => setTimeout(resolve, 10)); + + // No notifications should be received after disposal + expect(receivedNotifications.length).toBe(0); + }); + + test('is idempotent (can be called multiple times)', async () => { + const requestPromise = client.request('test')(); + await new Promise(resolve => setTimeout(resolve, 0)); + + // Call dispose multiple times + client.dispose(); + client.dispose(); + client.dispose(); + + // Should not throw, request should still be rejected once + const result = await requestPromise; + expect(E.isLeft(result)).toBe(true); + }); + }); + + describe('line buffering', () => { + test('handles partial lines', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Send partial line, then complete it + mockStreams.stdout.push('{"jsonrpc":"2.0",'); + mockStreams.stdout.push('"method":"partial"}\n'); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(receivedNotifications.length).toBe(1); + expect(receivedNotifications[0]).toEqual({ jsonrpc: '2.0', method: 'partial' }); + }); + + test('handles multiple lines in single chunk', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Send multiple lines at once + const multiLine = '{"jsonrpc":"2.0","method":"first"}\n{"jsonrpc":"2.0","method":"second"}\n'; + mockStreams.stdout.push(multiLine); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(receivedNotifications.length).toBe(2); + }); + + test('ignores empty lines', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + mockStreams.stdout.push('\n\n{"jsonrpc":"2.0","method":"test"}\n\n'); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(receivedNotifications.length).toBe(1); + }); + }); + + describe('error handling', () => { + test('handles malformed JSON gracefully', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Send invalid JSON + mockStreams.pushResponse('not valid json'); + + await new Promise(resolve => setTimeout(resolve, 10)); + + // Should not crash, just log error and continue + expect(receivedNotifications.length).toBe(0); + }); + + test('continues processing after malformed JSON', async () => { + const receivedNotifications: unknown[] = []; + + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Send bad JSON followed by good JSON + mockStreams.pushResponse('bad json'); + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', method: 'valid' })); + + await new Promise(resolve => setTimeout(resolve, 10)); + + // Should process the valid message + expect(receivedNotifications.length).toBe(1); + expect((receivedNotifications[0] as { method: string }).method).toBe('valid'); + }); + + test('logs warning for response with unknown ID', async () => { + // This test verifies the client handles unknown IDs gracefully + // by not throwing and continuing operation + + const receivedNotifications: unknown[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Push response for non-existent request + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', id: 999, result: 'orphan' })); + + // Push a valid notification after + mockStreams.pushResponse(JSON.stringify({ jsonrpc: '2.0', method: 'after.orphan' })); + + await new Promise(resolve => setTimeout(resolve, 10)); + + // Should still process subsequent messages + expect(receivedNotifications.length).toBe(1); + expect((receivedNotifications[0] as { method: string }).method).toBe('after.orphan'); + }); + }); +}); diff --git a/src/extension/jsonRpcClient.ts b/src/extension/jsonRpcClient.ts new file mode 100644 index 0000000..ca1b09b --- /dev/null +++ b/src/extension/jsonRpcClient.ts @@ -0,0 +1,228 @@ +/** + * JSON-RPC 2.0 client for communication with goose acp subprocess. + * Uses newline-delimited JSON (ndjson) framing over stdin/stdout. + */ + +import * as E from 'fp-ts/Either'; +import * as TE from 'fp-ts/TaskEither'; +import { Readable, Writable } from 'stream'; +import { + createJsonRpcError, + createJsonRpcParseError, + createJsonRpcTimeoutError, + JsonRpcError, + JsonRpcParseError, + JsonRpcTimeoutError, +} from '../shared/errors'; +import { JsonRpcNotification, JsonRpcRequest, JsonRpcResponse } from '../shared/types'; +import { Logger } from './logger'; + +const DEFAULT_TIMEOUT_MS = 30000; + +/** Configuration for creating a JSON-RPC client */ +export interface JsonRpcClientConfig { + readonly stdin: Writable; + readonly stdout: Readable; + readonly logger: Logger; + readonly timeoutMs?: number; +} + +/** Pending request tracking */ +interface PendingRequestEntry { + readonly id: number; + readonly method: string; + readonly resolve: (value: unknown) => void; + readonly reject: (error: JsonRpcError | JsonRpcTimeoutError) => void; + readonly timer: ReturnType; +} + +/** Notification callback type */ +export type NotificationCallback = (notification: JsonRpcNotification) => void; + +/** JSON-RPC client interface */ +export interface JsonRpcClient { + readonly request: ( + method: string, + params?: unknown + ) => TE.TaskEither; + + readonly notify: (method: string, params?: unknown) => E.Either; + + readonly onNotification: (callback: NotificationCallback) => void; + + readonly dispose: () => void; +} + +/** Create a JSON-RPC client */ +export function createJsonRpcClient(config: JsonRpcClientConfig): JsonRpcClient { + const { stdin, stdout, logger } = config; + const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS; + + let nextId = 1; + const pendingRequests = new Map(); + const notificationCallbacks: NotificationCallback[] = []; + let buffer = ''; + let disposed = false; + + const handleLine = (line: string): void => { + if (disposed) return; + + logger.debug('Received:', line); + + try { + const message = JSON.parse(line) as JsonRpcResponse | JsonRpcNotification; + + if ('id' in message && message.id !== undefined) { + const pending = pendingRequests.get(message.id); + if (pending) { + clearTimeout(pending.timer); + pendingRequests.delete(message.id); + + const response = message as JsonRpcResponse; + if (response.error) { + pending.reject( + createJsonRpcError(response.error.code, response.error.message, response.error.data) + ); + } else { + pending.resolve(response.result); + } + } else { + logger.warn(`Received response for unknown request id: ${message.id}`); + } + } else { + const notification = message as JsonRpcNotification; + logger.debug(`Notification: ${notification.method}`); + for (const callback of notificationCallbacks) { + try { + callback(notification); + } catch (err) { + logger.error('Notification callback error:', err); + } + } + } + } catch (err) { + const parseError = err instanceof Error ? err.message : String(err); + logger.error('JSON parse error:', parseError, 'raw:', line); + } + }; + + const onData = (chunk: Buffer): void => { + if (disposed) return; + + buffer += chunk.toString('utf8'); + const lines = buffer.split('\n'); + buffer = lines.pop() ?? ''; + + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed) { + handleLine(trimmed); + } + } + }; + + stdout.on('data', onData); + + const request = ( + method: string, + params?: unknown + ): TE.TaskEither => { + return () => + new Promise(resolve => { + if (disposed) { + resolve(E.left(createJsonRpcError(-32000, 'Client disposed'))); + return; + } + + const id = nextId++; + + const rpcRequest: JsonRpcRequest = { + jsonrpc: '2.0', + id, + method, + ...(params !== undefined && { params }), + }; + + const timer = setTimeout(() => { + const pending = pendingRequests.get(id); + if (pending) { + pendingRequests.delete(id); + resolve(E.left(createJsonRpcTimeoutError(method, timeoutMs, id))); + } + }, timeoutMs); + + const entry: PendingRequestEntry = { + id, + method, + resolve: value => resolve(E.right(value as T)), + reject: error => resolve(E.left(error)), + timer, + }; + + pendingRequests.set(id, entry); + + const requestLine = JSON.stringify(rpcRequest) + '\n'; + logger.debug('Sending:', requestLine.trim()); + + stdin.write(requestLine, err => { + if (err) { + clearTimeout(timer); + pendingRequests.delete(id); + resolve(E.left(createJsonRpcError(-32000, `Write error: ${err.message}`))); + } + }); + }); + }; + + const notify = (method: string, params?: unknown): E.Either => { + if (disposed) { + return E.left(createJsonRpcParseError('', 'Client disposed')); + } + + const notification: JsonRpcNotification = { + jsonrpc: '2.0', + method, + ...(params !== undefined && { params }), + }; + + const notificationLine = JSON.stringify(notification) + '\n'; + logger.debug('Sending notification:', notificationLine.trim()); + + try { + stdin.write(notificationLine); + return E.right(undefined); + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + return E.left(createJsonRpcParseError(notificationLine, errorMsg)); + } + }; + + const onNotification = (callback: NotificationCallback): void => { + notificationCallbacks.push(callback); + }; + + const dispose = (): void => { + if (disposed) return; + disposed = true; + + stdout.removeListener('data', onData); + + for (const [id, entry] of pendingRequests) { + clearTimeout(entry.timer); + entry.reject(createJsonRpcError(-32000, 'Client disposed')); + pendingRequests.delete(id); + } + + notificationCallbacks.length = 0; + buffer = ''; + + logger.debug('JSON-RPC client disposed'); + }; + + return { + request, + notify, + onNotification, + dispose, + }; +} diff --git a/src/extension/logger.ts b/src/extension/logger.ts new file mode 100644 index 0000000..70b270a --- /dev/null +++ b/src/extension/logger.ts @@ -0,0 +1,72 @@ +/** + * Source-tagged logger for the Goose extension. + * Uses VS Code OutputChannel for log output. + */ + +import * as vscode from 'vscode'; +import { LogLevel, logLevelToString } from '../shared/types'; + +/** Logger interface with source tagging support */ +export interface Logger { + readonly debug: (message: string, ...args: unknown[]) => void; + readonly info: (message: string, ...args: unknown[]) => void; + readonly warn: (message: string, ...args: unknown[]) => void; + readonly error: (message: string, ...args: unknown[]) => void; + readonly child: (source: string) => Logger; + readonly setLevel: (level: LogLevel) => void; +} + +/** Format a timestamp for log messages */ +function formatTimestamp(date: Date): string { + return date.toISOString().slice(11, 23); +} + +/** Format additional arguments for logging */ +function formatArgs(args: unknown[]): string { + if (args.length === 0) return ''; + return ( + ' ' + + args + .map(arg => { + if (typeof arg === 'string') return arg; + try { + return JSON.stringify(arg); + } catch { + return String(arg); + } + }) + .join(' ') + ); +} + +/** Create a logger that writes to a VS Code OutputChannel */ +export function createLogger( + outputChannel: vscode.OutputChannel, + initialLevel: LogLevel, + source?: string +): Logger { + let currentLevel = initialLevel; + + const log = (level: LogLevel, message: string, ...args: unknown[]): void => { + if (level < currentLevel) return; + + const timestamp = formatTimestamp(new Date()); + const levelStr = logLevelToString(level); + const sourceStr = source ? `[${source}] ` : ''; + const argsStr = formatArgs(args); + + outputChannel.appendLine(`${timestamp} [${levelStr}] ${sourceStr}${message}${argsStr}`); + }; + + return { + debug: (message: string, ...args: unknown[]) => log(LogLevel.DEBUG, message, ...args), + info: (message: string, ...args: unknown[]) => log(LogLevel.INFO, message, ...args), + warn: (message: string, ...args: unknown[]) => log(LogLevel.WARN, message, ...args), + error: (message: string, ...args: unknown[]) => log(LogLevel.ERROR, message, ...args), + child: (childSource: string) => + createLogger(outputChannel, currentLevel, source ? `${source}:${childSource}` : childSource), + setLevel: (level: LogLevel) => { + currentLevel = level; + }, + }; +} diff --git a/src/extension/sessionManager.ts b/src/extension/sessionManager.ts new file mode 100644 index 0000000..1e9f066 --- /dev/null +++ b/src/extension/sessionManager.ts @@ -0,0 +1,325 @@ +/** + * Session manager for orchestrating session operations. + * Coordinates between ACP client, session storage, and webview. + */ + +import { pipe } from 'fp-ts/function'; +import * as TE from 'fp-ts/TaskEither'; +import { createJsonRpcError, GooseError } from '../shared/errors'; +import { + AgentCapabilities, + DEFAULT_CAPABILITIES, + GroupedSessions, + generateSessionTitle, + groupSessionsByDate, + SessionEntry, +} from '../shared/sessionTypes'; +import { ChatMessage, JsonRpcNotification, MessageRole, MessageStatus } from '../shared/types'; +import { JsonRpcClient } from './jsonRpcClient'; +import { Logger } from './logger'; +import { SessionStorage } from './sessionStorage'; + +interface AcpSessionNewResponse { + readonly sessionId: string; +} + +interface AcpSessionLoadResponse { + readonly success?: boolean; +} + +/** ContentBlock types from ACP */ +interface AcpTextContent { + readonly type: 'text'; + readonly text: string; +} + +interface AcpResourceLinkContent { + readonly type: 'resource_link'; + readonly uri: string; + readonly name: string; + readonly mimeType?: string; +} + +interface AcpEmbeddedResourceContent { + readonly type: 'resource'; + readonly resource: { + readonly uri: string; + readonly text?: string; + readonly blob?: string; + readonly mimeType?: string; + }; +} + +type AcpContentBlock = AcpTextContent | AcpResourceLinkContent | AcpEmbeddedResourceContent; + +interface AcpSessionUpdateParams { + readonly sessionId: string; + readonly update: { + readonly sessionUpdate: string; + readonly content?: AcpContentBlock; + }; +} + +export interface SessionManager { + initialize( + client: JsonRpcClient, + capabilities: AgentCapabilities, + workingDirectory: string + ): void; + createSession(): TE.TaskEither; + loadSession(sessionId: string): TE.TaskEither; + getGroupedSessions(): GroupedSessions[]; + getSessions(): readonly SessionEntry[]; + getActiveSession(): SessionEntry | null; + getActiveSessionId(): string | null; + updateSessionTitle(sessionId: string, title: string): Promise; + hasLoadSessionCapability(): boolean; + hasEmbeddedContextCapability(): boolean; + onHistoryMessage(callback: (message: ChatMessage) => void): () => void; + onHistoryComplete(callback: (sessionId: string, messageCount: number) => void): () => void; + dispose(): void; +} + +export function createSessionManager(storage: SessionStorage, logger: Logger): SessionManager { + let client: JsonRpcClient | null = null; + let capabilities: AgentCapabilities = DEFAULT_CAPABILITIES; + let workingDirectory = ''; + const historyMessageCallbacks: ((message: ChatMessage) => void)[] = []; + const historyCompleteCallbacks: ((sessionId: string, messageCount: number) => void)[] = []; + + const generateId = (): string => { + return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; + }; + + const initialize = ( + rpcClient: JsonRpcClient, + agentCapabilities: AgentCapabilities, + cwd: string + ): void => { + client = rpcClient; + capabilities = agentCapabilities; + workingDirectory = cwd; + logger.info('SessionManager initialized'); + }; + + const createSession = (): TE.TaskEither => { + if (!client) { + return TE.left(createJsonRpcError(-32000, 'Client not initialized')); + } + + const rpcClient = client; + + return pipe( + rpcClient.request('session/new', { + cwd: workingDirectory, + mcpServers: [], + }), + TE.map(response => { + const session: SessionEntry = { + sessionId: response.sessionId, + title: 'New Session', + cwd: workingDirectory, + createdAt: new Date().toISOString(), + }; + + storage.addSession(session); + storage.setActiveSession(session.sessionId); + + logger.info(`Created session: ${session.sessionId}`); + return session; + }) + ); + }; + + const loadSession = (sessionId: string): TE.TaskEither => { + if (!client) { + return TE.left(createJsonRpcError(-32000, 'Client not initialized')); + } + + const session = storage.getSession(sessionId); + if (!session) { + return TE.left(createJsonRpcError(-32001, `Session not found: ${sessionId}`)); + } + + if (!capabilities.loadSession) { + logger.info('loadSession capability not available, switching without history'); + storage.setActiveSession(sessionId); + return TE.right(undefined); + } + + const rpcClient = client; + let messageCount = 0; + let isLoadingSession = true; + + rpcClient.onNotification((notification: JsonRpcNotification) => { + if (!isLoadingSession) return; + if (notification.method !== 'session/update') return; + + const params = notification.params as AcpSessionUpdateParams | undefined; + if (!params?.update) return; + + const { sessionUpdate, content } = params.update; + if (!content) return; + + const role = + sessionUpdate === 'user_message_chunk' + ? MessageRole.USER + : sessionUpdate === 'agent_message_chunk' + ? MessageRole.ASSISTANT + : null; + + if (!role) return; + + // Handle different content types + if (content.type === 'text') { + const msg: ChatMessage = { + id: generateId(), + role, + content: content.text, + timestamp: undefined, + status: MessageStatus.COMPLETE, + }; + historyMessageCallbacks.forEach(cb => cb(msg)); + messageCount++; + } else if (content.type === 'resource_link') { + // Resource link - reference without content + const fileName = content.name || content.uri.split('/').pop() || 'file'; + const filePath = content.uri.replace(/^file:\/\//, ''); + const msg: ChatMessage = { + id: generateId(), + role, + content: '', // No text content + timestamp: undefined, + status: MessageStatus.COMPLETE, + context: [ + { + filePath, + fileName, + }, + ], + }; + historyMessageCallbacks.forEach(cb => cb(msg)); + messageCount++; + } else if (content.type === 'resource') { + // Embedded resource - has actual content + const uri = content.resource.uri; + const filePath = uri.replace(/^file:\/\//, '').split('#')[0]; + const fileName = filePath.split('/').pop() || 'file'; + const fileContent = content.resource.text || ''; + const msg: ChatMessage = { + id: generateId(), + role, + content: '', // No text content, it's in context + timestamp: undefined, + status: MessageStatus.COMPLETE, + context: [ + { + filePath, + fileName, + content: fileContent, + }, + ], + }; + historyMessageCallbacks.forEach(cb => cb(msg)); + messageCount++; + } + }); + + return pipe( + rpcClient.request('session/load', { + sessionId, + cwd: session.cwd, + mcpServers: [], + }), + TE.map(() => { + isLoadingSession = false; + storage.setActiveSession(sessionId); + historyCompleteCallbacks.forEach(cb => cb(sessionId, messageCount)); + logger.info(`Loaded session: ${sessionId} with ${messageCount} messages`); + }), + TE.mapLeft(error => { + isLoadingSession = false; + logger.error('Failed to load session:', error); + return error; + }) + ); + }; + + const getGroupedSessions = (): GroupedSessions[] => { + const sessions = storage.getSessions(); + return groupSessionsByDate(sessions); + }; + + const getSessions = (): readonly SessionEntry[] => { + return storage.getSessions(); + }; + + const getActiveSession = (): SessionEntry | null => { + const activeId = storage.getActiveSessionId(); + if (!activeId) return null; + return storage.getSession(activeId) ?? null; + }; + + const getActiveSessionId = (): string | null => { + return storage.getActiveSessionId(); + }; + + const updateSessionTitle = async (sessionId: string, title: string): Promise => { + const generatedTitle = generateSessionTitle(title); + await storage.updateSessionTitle(sessionId, generatedTitle); + logger.debug(`Updated session title: ${sessionId} -> ${generatedTitle}`); + }; + + const hasLoadSessionCapability = (): boolean => { + return capabilities.loadSession; + }; + + const hasEmbeddedContextCapability = (): boolean => { + return capabilities.promptCapabilities.embeddedContext; + }; + + const onHistoryMessage = (callback: (message: ChatMessage) => void): (() => void) => { + historyMessageCallbacks.push(callback); + return () => { + const index = historyMessageCallbacks.indexOf(callback); + if (index > -1) { + historyMessageCallbacks.splice(index, 1); + } + }; + }; + + const onHistoryComplete = ( + callback: (sessionId: string, messageCount: number) => void + ): (() => void) => { + historyCompleteCallbacks.push(callback); + return () => { + const index = historyCompleteCallbacks.indexOf(callback); + if (index > -1) { + historyCompleteCallbacks.splice(index, 1); + } + }; + }; + + const dispose = (): void => { + client = null; + historyMessageCallbacks.length = 0; + historyCompleteCallbacks.length = 0; + logger.debug('SessionManager disposed'); + }; + + return { + initialize, + createSession, + loadSession, + getGroupedSessions, + getSessions, + getActiveSession, + getActiveSessionId, + updateSessionTitle, + hasLoadSessionCapability, + hasEmbeddedContextCapability, + onHistoryMessage, + onHistoryComplete, + dispose, + }; +} diff --git a/src/extension/sessionStorage.test.ts b/src/extension/sessionStorage.test.ts new file mode 100644 index 0000000..d7a4ed6 --- /dev/null +++ b/src/extension/sessionStorage.test.ts @@ -0,0 +1,294 @@ +import { beforeEach, describe, expect, test } from 'bun:test'; +import { SessionEntry } from '../shared/sessionTypes'; +import { createMockMemento, MockMemento } from '../test/mocks/vscode'; +import { createSessionStorage, SessionStorage } from './sessionStorage'; + +describe('SessionStorage', () => { + let memento: MockMemento; + let storage: SessionStorage; + + const createSession = (id: string, title = 'Test Session'): SessionEntry => ({ + sessionId: id, + title, + cwd: '/test/path', + createdAt: new Date().toISOString(), + }); + + beforeEach(() => { + memento = createMockMemento(); + storage = createSessionStorage(memento); + }); + + describe('getSessions', () => { + test('returns empty array when no sessions stored', () => { + const sessions = storage.getSessions(); + expect(sessions).toEqual([]); + }); + + test('returns stored sessions in order', async () => { + const session1 = createSession('session-1', 'First'); + const session2 = createSession('session-2', 'Second'); + + await storage.addSession(session1); + await storage.addSession(session2); + + const sessions = storage.getSessions(); + expect(sessions).toHaveLength(2); + expect(sessions[0].sessionId).toBe('session-2'); + expect(sessions[1].sessionId).toBe('session-1'); + }); + }); + + describe('addSession', () => { + test('adds new session to front of list', async () => { + const session1 = createSession('session-1'); + const session2 = createSession('session-2'); + + await storage.addSession(session1); + await storage.addSession(session2); + + const sessions = storage.getSessions(); + expect(sessions).toHaveLength(2); + expect(sessions[0].sessionId).toBe('session-2'); + }); + + test('updates existing session when sessionId matches', async () => { + const session = createSession('session-1', 'Original Title'); + await storage.addSession(session); + + const updatedSession = { ...session, title: 'Updated Title' }; + await storage.addSession(updatedSession); + + const sessions = storage.getSessions(); + expect(sessions).toHaveLength(1); + expect(sessions[0].title).toBe('Updated Title'); + }); + + test('preserves position when updating existing session', async () => { + const session1 = createSession('session-1', 'First'); + const session2 = createSession('session-2', 'Second'); + const session3 = createSession('session-3', 'Third'); + + await storage.addSession(session1); + await storage.addSession(session2); + await storage.addSession(session3); + + const updatedSession2 = { ...session2, title: 'Updated Second' }; + await storage.addSession(updatedSession2); + + const sessions = storage.getSessions(); + expect(sessions).toHaveLength(3); + expect(sessions[1].title).toBe('Updated Second'); + }); + }); + + describe('getSession', () => { + test('returns session by ID', async () => { + const session = createSession('session-1', 'My Session'); + await storage.addSession(session); + + const result = storage.getSession('session-1'); + expect(result).toBeDefined(); + expect(result?.sessionId).toBe('session-1'); + expect(result?.title).toBe('My Session'); + }); + + test('returns undefined for missing ID', async () => { + const session = createSession('session-1'); + await storage.addSession(session); + + const result = storage.getSession('non-existent'); + expect(result).toBeUndefined(); + }); + + test('returns undefined when no sessions exist', () => { + const result = storage.getSession('any-id'); + expect(result).toBeUndefined(); + }); + }); + + describe('updateSessionTitle', () => { + test('updates title of existing session', async () => { + const session = createSession('session-1', 'Original'); + await storage.addSession(session); + + await storage.updateSessionTitle('session-1', 'New Title'); + + const result = storage.getSession('session-1'); + expect(result?.title).toBe('New Title'); + }); + + test('no-op for missing session', async () => { + const session = createSession('session-1', 'Original'); + await storage.addSession(session); + + await storage.updateSessionTitle('non-existent', 'New Title'); + + const sessions = storage.getSessions(); + expect(sessions).toHaveLength(1); + expect(sessions[0].title).toBe('Original'); + }); + + test('preserves other session fields', async () => { + const session = createSession('session-1', 'Original'); + await storage.addSession(session); + + await storage.updateSessionTitle('session-1', 'New Title'); + + const result = storage.getSession('session-1'); + expect(result?.cwd).toBe('/test/path'); + expect(result?.createdAt).toBe(session.createdAt); + }); + }); + + describe('setActiveSession', () => { + test('stores active session ID', async () => { + await storage.setActiveSession('session-1'); + + const activeId = storage.getActiveSessionId(); + expect(activeId).toBe('session-1'); + }); + + test('overwrites previous active session', async () => { + await storage.setActiveSession('session-1'); + await storage.setActiveSession('session-2'); + + const activeId = storage.getActiveSessionId(); + expect(activeId).toBe('session-2'); + }); + }); + + describe('getActiveSessionId', () => { + test('returns null when no active session', () => { + const activeId = storage.getActiveSessionId(); + expect(activeId).toBeNull(); + }); + + test('returns active session ID when set', async () => { + await storage.setActiveSession('session-1'); + + const activeId = storage.getActiveSessionId(); + expect(activeId).toBe('session-1'); + }); + }); + + describe('removeSession', () => { + test('removes session from list', async () => { + const session1 = createSession('session-1'); + const session2 = createSession('session-2'); + + await storage.addSession(session1); + await storage.addSession(session2); + + await storage.removeSession('session-1'); + + const sessions = storage.getSessions(); + expect(sessions).toHaveLength(1); + expect(sessions[0].sessionId).toBe('session-2'); + }); + + test('clears active session when removed session was active', async () => { + const session = createSession('session-1'); + await storage.addSession(session); + await storage.setActiveSession('session-1'); + + await storage.removeSession('session-1'); + + const activeId = storage.getActiveSessionId(); + expect(activeId).toBeNull(); + }); + + test('preserves active session when different session removed', async () => { + const session1 = createSession('session-1'); + const session2 = createSession('session-2'); + + await storage.addSession(session1); + await storage.addSession(session2); + await storage.setActiveSession('session-1'); + + await storage.removeSession('session-2'); + + const activeId = storage.getActiveSessionId(); + expect(activeId).toBe('session-1'); + }); + + test('no-op when removing non-existent session', async () => { + const session = createSession('session-1'); + await storage.addSession(session); + + await storage.removeSession('non-existent'); + + const sessions = storage.getSessions(); + expect(sessions).toHaveLength(1); + }); + }); + + describe('clearAll', () => { + test('removes all sessions', async () => { + await storage.addSession(createSession('session-1')); + await storage.addSession(createSession('session-2')); + + await storage.clearAll(); + + const sessions = storage.getSessions(); + expect(sessions).toEqual([]); + }); + + test('clears active session', async () => { + await storage.addSession(createSession('session-1')); + await storage.setActiveSession('session-1'); + + await storage.clearAll(); + + const activeId = storage.getActiveSessionId(); + expect(activeId).toBeNull(); + }); + + test('removes storage keys from memento', async () => { + await storage.addSession(createSession('session-1')); + await storage.setActiveSession('session-1'); + + await storage.clearAll(); + + expect(memento.keys()).toEqual([]); + }); + }); + + describe('schema migration', () => { + test('returns empty array for mismatched schema version', async () => { + await memento.update('goose.sessions.v1', { + schemaVersion: 999, + activeSessionId: null, + sessions: [createSession('session-1')], + }); + + storage = createSessionStorage(memento); + const sessions = storage.getSessions(); + expect(sessions).toEqual([]); + }); + + test('returns empty array when no schema version present', async () => { + await memento.update('goose.sessions.v1', { + sessions: [createSession('session-1')], + }); + + storage = createSessionStorage(memento); + const sessions = storage.getSessions(); + expect(sessions).toEqual([]); + }); + + test('returns sessions for matching schema version', async () => { + const session = createSession('session-1'); + await memento.update('goose.sessions.v1', { + schemaVersion: 1, + activeSessionId: null, + sessions: [session], + }); + + storage = createSessionStorage(memento); + const sessions = storage.getSessions(); + expect(sessions).toHaveLength(1); + expect(sessions[0].sessionId).toBe('session-1'); + }); + }); +}); diff --git a/src/extension/sessionStorage.ts b/src/extension/sessionStorage.ts new file mode 100644 index 0000000..519ce6b --- /dev/null +++ b/src/extension/sessionStorage.ts @@ -0,0 +1,103 @@ +/** + * Session storage for persisting session metadata in VS Code globalState. + * Stores minimal data: session ID, title, cwd, and createdAt. + */ + +import * as vscode from 'vscode'; +import { SessionEntry, SessionStorageData } from '../shared/sessionTypes'; + +const STORAGE_KEY_SESSIONS = 'goose.sessions.v1'; +const STORAGE_KEY_ACTIVE = 'goose.activeSession'; +const CURRENT_SCHEMA_VERSION = 1; + +export interface SessionStorage { + getSessions(): readonly SessionEntry[]; + getSession(sessionId: string): SessionEntry | undefined; + getActiveSessionId(): string | null; + addSession(session: SessionEntry): Promise; + updateSessionTitle(sessionId: string, title: string): Promise; + setActiveSession(sessionId: string): Promise; + removeSession(sessionId: string): Promise; + clearAll(): Promise; +} + +export function createSessionStorage(globalState: vscode.Memento): SessionStorage { + const loadSessions = (): SessionEntry[] => { + const data = globalState.get(STORAGE_KEY_SESSIONS); + if (!data || data.schemaVersion !== CURRENT_SCHEMA_VERSION) { + return []; + } + return [...data.sessions]; + }; + + const saveSessions = async (sessions: SessionEntry[]): Promise => { + const data: SessionStorageData = { + schemaVersion: CURRENT_SCHEMA_VERSION, + activeSessionId: getActiveSessionId(), + sessions, + }; + await globalState.update(STORAGE_KEY_SESSIONS, data); + }; + + const getSessions = (): readonly SessionEntry[] => { + return loadSessions(); + }; + + const getSession = (sessionId: string): SessionEntry | undefined => { + const sessions = loadSessions(); + return sessions.find(s => s.sessionId === sessionId); + }; + + const getActiveSessionId = (): string | null => { + return globalState.get(STORAGE_KEY_ACTIVE, null); + }; + + const addSession = async (session: SessionEntry): Promise => { + const sessions = loadSessions(); + const existing = sessions.findIndex(s => s.sessionId === session.sessionId); + if (existing >= 0) { + sessions[existing] = session; + } else { + sessions.unshift(session); + } + await saveSessions(sessions); + }; + + const updateSessionTitle = async (sessionId: string, title: string): Promise => { + const sessions = loadSessions(); + const index = sessions.findIndex(s => s.sessionId === sessionId); + if (index >= 0) { + sessions[index] = { ...sessions[index], title }; + await saveSessions(sessions); + } + }; + + const setActiveSession = async (sessionId: string): Promise => { + await globalState.update(STORAGE_KEY_ACTIVE, sessionId); + }; + + const removeSession = async (sessionId: string): Promise => { + const sessions = loadSessions().filter(s => s.sessionId !== sessionId); + await saveSessions(sessions); + const activeId = getActiveSessionId(); + if (activeId === sessionId) { + await globalState.update(STORAGE_KEY_ACTIVE, null); + } + }; + + const clearAll = async (): Promise => { + await globalState.update(STORAGE_KEY_SESSIONS, undefined); + await globalState.update(STORAGE_KEY_ACTIVE, undefined); + }; + + return { + getSessions, + getSession, + getActiveSessionId, + addSession, + updateSessionTitle, + setActiveSession, + removeSession, + clearAll, + }; +} diff --git a/src/extension/subprocess.integration.test.ts b/src/extension/subprocess.integration.test.ts new file mode 100644 index 0000000..08aad70 --- /dev/null +++ b/src/extension/subprocess.integration.test.ts @@ -0,0 +1,730 @@ +/** + * Integration tests for subprocess communication via JSON-RPC. + * Uses a MockSubprocess simulator to test end-to-end message flows + * without spawning a real subprocess. + */ + +import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; +import * as E from 'fp-ts/Either'; +import { Readable, Writable } from 'stream'; +import { isJsonRpcError } from '../shared/errors'; +import { JsonRpcNotification, JsonRpcRequest, JsonRpcResponse } from '../shared/types'; +import { createJsonRpcClient, JsonRpcClient, JsonRpcClientConfig } from './jsonRpcClient'; +import { Logger } from './logger'; + +// ============================================================================ +// Mock Subprocess Simulator +// ============================================================================ + +/** Handler function type for mock subprocess methods */ +type MethodHandler = (params: unknown) => unknown; + +/** Interface for the bidirectional mock streams */ +interface BidirectionalMockStreams { + /** Stream where client writes requests (subprocess reads) */ + readonly clientToSubprocess: Writable; + /** Stream where subprocess writes responses (client reads) */ + readonly subprocessToClient: Readable; + /** Close the subprocess output stream */ + close: () => void; +} + +/** + * Creates bidirectional mock streams for subprocess simulation. + * The client writes to clientToSubprocess, and the subprocess + * reads those writes via onRequest callback and writes responses + * to subprocessToClient. + */ +function createBidirectionalMockStreams( + onRequest: (data: string) => void +): BidirectionalMockStreams { + // Client writes here, we intercept and call onRequest + const clientToSubprocess = new Writable({ + write(chunk, _encoding, callback) { + onRequest(chunk.toString()); + callback(); + }, + }); + + // Subprocess writes here, client reads from here + const subprocessToClient = new Readable({ + read() { + // No-op: data is pushed via push() + }, + }); + + return { + clientToSubprocess, + subprocessToClient, + close: () => { + subprocessToClient.push(null); + }, + }; +} + +/** + * MockSubprocess simulates a subprocess that responds to JSON-RPC requests. + * It processes requests via registered handlers and writes responses back. + */ +class MockSubprocess { + private handlers: Map = new Map(); + private streams: BidirectionalMockStreams | null = null; + private shouldCrash: boolean = false; + private crashAfterRequests: number = -1; + private requestCount: number = 0; + private buffer: string = ''; + + /** + * Register a handler for a specific JSON-RPC method + */ + registerHandler(method: string, handler: MethodHandler): void { + this.handlers.set(method, handler); + } + + /** + * Process an incoming JSON-RPC request and return appropriate response + */ + handleRequest(request: JsonRpcRequest): JsonRpcResponse { + const handler = this.handlers.get(request.method); + if (handler) { + try { + const result = handler(request.params); + return { + jsonrpc: '2.0', + id: request.id, + result, + }; + } catch (err) { + return { + jsonrpc: '2.0', + id: request.id, + error: { + code: -32000, + message: err instanceof Error ? err.message : String(err), + }, + }; + } + } + return { + jsonrpc: '2.0', + id: request.id, + error: { code: -32601, message: 'Method not found' }, + }; + } + + /** + * Create streams and start processing + * Returns the streams for the client to use + */ + createStreams(): BidirectionalMockStreams { + this.streams = createBidirectionalMockStreams((data: string) => { + this.processIncomingData(data); + }); + return this.streams; + } + + /** + * Process incoming data from client + */ + private processIncomingData(data: string): void { + if (this.shouldCrash || !this.streams) { + return; + } + + this.buffer += data; + const lines = this.buffer.split('\n'); + this.buffer = lines.pop() ?? ''; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + this.requestCount++; + + // Check if we should crash after this request + if (this.crashAfterRequests > 0 && this.requestCount >= this.crashAfterRequests) { + this.shouldCrash = true; + this.streams.close(); + return; + } + + try { + const request = JSON.parse(trimmed) as JsonRpcRequest; + + // Only respond to requests (have id), not notifications + if ('id' in request && request.id !== undefined) { + const response = this.handleRequest(request); + this.pushResponse(JSON.stringify(response)); + } + } catch { + // Malformed JSON - in a real subprocess this might crash or log error + // We simulate graceful handling by not responding + } + } + } + + /** + * Push a response to the client + */ + private pushResponse(data: string): void { + if (this.streams && !this.shouldCrash) { + this.streams.subprocessToClient.push(data + '\n'); + } + } + + /** + * Send a notification from the subprocess to the client + */ + sendNotification(method: string, params?: unknown): void { + if (this.shouldCrash || !this.streams) return; + + const notification: JsonRpcNotification = { + jsonrpc: '2.0', + method, + ...(params !== undefined && { params }), + }; + this.pushResponse(JSON.stringify(notification)); + } + + /** + * Configure the subprocess to crash after N requests + */ + crashAfter(requests: number): void { + this.crashAfterRequests = requests; + } + + /** + * Simulate an immediate crash + */ + crash(): void { + this.shouldCrash = true; + if (this.streams) { + this.streams.close(); + } + } + + /** + * Send malformed JSON to test error handling + */ + sendMalformedResponse(data: string): void { + if (this.streams && !this.shouldCrash) { + this.streams.subprocessToClient.push(data + '\n'); + } + } + + /** + * Push empty line + */ + pushEmptyLine(): void { + if (this.streams && !this.shouldCrash) { + this.streams.subprocessToClient.push('\n'); + } + } +} + +// ============================================================================ +// Test Utilities +// ============================================================================ + +/** Create a no-op logger for testing */ +function createMockLogger(): Logger { + const noop = () => undefined; + return { + debug: noop, + info: noop, + warn: noop, + error: noop, + }; +} + +// ============================================================================ +// Integration Tests +// ============================================================================ + +describe('Subprocess Integration Tests', () => { + let client: JsonRpcClient; + let subprocess: MockSubprocess; + let streams: BidirectionalMockStreams; + + beforeEach(() => { + subprocess = new MockSubprocess(); + streams = subprocess.createStreams(); + + const config: JsonRpcClientConfig = { + stdin: streams.clientToSubprocess, + stdout: streams.subprocessToClient, + logger: createMockLogger(), + timeoutMs: 1000, + }; + client = createJsonRpcClient(config); + }); + + afterEach(() => { + client.dispose(); + }); + + describe('Request/Response Round-Trip', () => { + test('completes successfully with simple handler', async () => { + subprocess.registerHandler('echo', params => ({ echoed: params })); + + const result = await client.request<{ echoed: unknown }>('echo', { message: 'hello' })(); + + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toEqual({ echoed: { message: 'hello' } }); + } + }); + + test('completes successfully with handler returning primitive', async () => { + subprocess.registerHandler('add', params => { + const p = params as { a: number; b: number }; + return p.a + p.b; + }); + + const result = await client.request('add', { a: 5, b: 3 })(); + + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe(8); + } + }); + + test('returns error for unregistered method', async () => { + const result = await client.request('unknown.method')(); + + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(isJsonRpcError(result.left)).toBe(true); + expect(result.left.code).toBe(-32601); + expect(result.left.message).toBe('Method not found'); + } + }); + + test('returns error when handler throws', async () => { + subprocess.registerHandler('failing', () => { + throw new Error('Handler error'); + }); + + const result = await client.request('failing')(); + + expect(E.isLeft(result)).toBe(true); + if (E.isLeft(result)) { + expect(isJsonRpcError(result.left)).toBe(true); + expect(result.left.message).toBe('Handler error'); + } + }); + + test('handles null result correctly', async () => { + subprocess.registerHandler('getNull', () => null); + + const result = await client.request('getNull')(); + + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBeNull(); + } + }); + + test('handles complex nested response correctly', async () => { + const complexData = { + users: [ + { id: 1, name: 'Alice', metadata: { role: 'admin' } }, + { id: 2, name: 'Bob', metadata: { role: 'user' } }, + ], + pagination: { page: 1, total: 100 }, + }; + subprocess.registerHandler('getComplex', () => complexData); + + const result = await client.request('getComplex')(); + + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toEqual(complexData); + } + }); + }); + + describe('Concurrent Requests', () => { + test('resolves multiple concurrent requests correctly', async () => { + subprocess.registerHandler('identify', params => { + const p = params as { id: string }; + return { response: `Response for ${p.id}` }; + }); + + const [result1, result2, result3] = await Promise.all([ + client.request<{ response: string }>('identify', { id: 'A' })(), + client.request<{ response: string }>('identify', { id: 'B' })(), + client.request<{ response: string }>('identify', { id: 'C' })(), + ]); + + expect(E.isRight(result1)).toBe(true); + expect(E.isRight(result2)).toBe(true); + expect(E.isRight(result3)).toBe(true); + + if (E.isRight(result1)) { + expect(result1.right.response).toBe('Response for A'); + } + if (E.isRight(result2)) { + expect(result2.right.response).toBe('Response for B'); + } + if (E.isRight(result3)) { + expect(result3.right.response).toBe('Response for C'); + } + }); + + test('handles mixed success and failure in concurrent requests', async () => { + subprocess.registerHandler('mayFail', params => { + const p = params as { shouldFail: boolean }; + if (p.shouldFail) { + throw new Error('Intentional failure'); + } + return { success: true }; + }); + + const [success1, failure, success2] = await Promise.all([ + client.request('mayFail', { shouldFail: false })(), + client.request('mayFail', { shouldFail: true })(), + client.request('mayFail', { shouldFail: false })(), + ]); + + expect(E.isRight(success1)).toBe(true); + expect(E.isLeft(failure)).toBe(true); + expect(E.isRight(success2)).toBe(true); + }); + + test('maintains request/response correlation under load', async () => { + subprocess.registerHandler('delay', params => { + const p = params as { value: number }; + return { doubled: p.value * 2 }; + }); + + // Fire 10 concurrent requests with different values + const requests = Array.from({ length: 10 }, (_, i) => + client.request<{ doubled: number }>('delay', { value: i })() + ); + + const results = await Promise.all(requests); + + // Verify all completed and returned correct values + results.forEach((result, i) => { + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right.doubled).toBe(i * 2); + } + }); + }); + }); + + describe('Notification Handling', () => { + test('receives notifications from subprocess', async () => { + const receivedNotifications: JsonRpcNotification[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + subprocess.sendNotification('server.event', { type: 'update', data: 'new data' }); + + // Wait for notification to be processed + await new Promise(resolve => setTimeout(resolve, 20)); + + expect(receivedNotifications.length).toBe(1); + expect(receivedNotifications[0].method).toBe('server.event'); + expect(receivedNotifications[0].params).toEqual({ type: 'update', data: 'new data' }); + }); + + test('receives multiple notifications in order', async () => { + const receivedNotifications: JsonRpcNotification[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + subprocess.sendNotification('event', { seq: 1 }); + subprocess.sendNotification('event', { seq: 2 }); + subprocess.sendNotification('event', { seq: 3 }); + + await new Promise(resolve => setTimeout(resolve, 20)); + + expect(receivedNotifications.length).toBe(3); + expect((receivedNotifications[0].params as { seq: number }).seq).toBe(1); + expect((receivedNotifications[1].params as { seq: number }).seq).toBe(2); + expect((receivedNotifications[2].params as { seq: number }).seq).toBe(3); + }); + + test('notifications do not interfere with pending requests', async () => { + subprocess.registerHandler('slow', () => 'done'); + + const receivedNotifications: JsonRpcNotification[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Start a request + const requestPromise = client.request('slow')(); + + // Send notifications while request is pending + subprocess.sendNotification('interrupt', { msg: 'notification 1' }); + subprocess.sendNotification('interrupt', { msg: 'notification 2' }); + + const result = await requestPromise; + await new Promise(resolve => setTimeout(resolve, 20)); + + // Request should complete successfully + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('done'); + } + + // Notifications should also be received + expect(receivedNotifications.length).toBe(2); + }); + + test('handles notifications without params', async () => { + const receivedNotifications: JsonRpcNotification[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + subprocess.sendNotification('ping'); + + await new Promise(resolve => setTimeout(resolve, 20)); + + expect(receivedNotifications.length).toBe(1); + expect(receivedNotifications[0].method).toBe('ping'); + expect(receivedNotifications[0].params).toBeUndefined(); + }); + }); + + describe('Subprocess Crash Handling', () => { + test('pending requests fail when subprocess crashes', async () => { + // Don't register any handler - request will not get a response + // And we crash the subprocess immediately + + // Start request + const requestPromise = client.request('neverCompletes')(); + + // Wait a tick then crash + await new Promise(resolve => setTimeout(resolve, 10)); + subprocess.crash(); + + const result = await requestPromise; + + // The client should timeout or receive an error when stream closes + // With a 1 second timeout, we'll get a timeout error + expect(E.isLeft(result)).toBe(true); + }); + + test('crash after N requests stops further responses', async () => { + let handlerCallCount = 0; + subprocess.registerHandler('countAndReturn', () => { + handlerCallCount++; + return { count: handlerCallCount }; + }); + + subprocess.crashAfter(2); + + // First request should succeed + const result1 = await client.request('countAndReturn')(); + expect(E.isRight(result1)).toBe(true); + + // Second request triggers crash - it may or may not complete + // depending on timing + const result2Promise = client.request('countAndReturn')(); + + // After crash, subsequent requests should timeout + await result2Promise; + // Result could be success (if crash happens after response) or timeout + // Just verify it doesn't hang + }); + + test('notifications stop after crash', async () => { + const receivedNotifications: JsonRpcNotification[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Send notification before crash + subprocess.sendNotification('before', { when: 'before' }); + await new Promise(resolve => setTimeout(resolve, 20)); + + // Crash + subprocess.crash(); + + // Try to send notification after crash (should be ignored) + subprocess.sendNotification('after', { when: 'after' }); + await new Promise(resolve => setTimeout(resolve, 20)); + + // Only notification before crash should be received + expect(receivedNotifications.length).toBe(1); + expect(receivedNotifications[0].method).toBe('before'); + }); + }); + + describe('Malformed JSON Handling', () => { + test('handles malformed JSON gracefully without crashing', async () => { + const receivedNotifications: JsonRpcNotification[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Send malformed JSON + subprocess.sendMalformedResponse('{ invalid json }'); + + // Wait a tick + await new Promise(resolve => setTimeout(resolve, 20)); + + // Client should not crash + // Send valid notification to verify client still works + subprocess.sendNotification('afterMalformed', { status: 'ok' }); + + await new Promise(resolve => setTimeout(resolve, 20)); + + // Should receive the valid notification + expect(receivedNotifications.length).toBe(1); + expect(receivedNotifications[0].method).toBe('afterMalformed'); + }); + + test('continues processing valid messages after malformed JSON', async () => { + subprocess.registerHandler('test', () => 'success'); + + // Send malformed JSON directly + subprocess.sendMalformedResponse('not json at all'); + + // Then make a valid request + const result = await client.request('test')(); + + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('success'); + } + }); + + test('handles truncated JSON gracefully', async () => { + const receivedNotifications: JsonRpcNotification[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Send truncated JSON + subprocess.sendMalformedResponse('{"jsonrpc":"2.0","method":"truncated"'); + + // Send valid notification + subprocess.sendNotification('valid', { data: 'ok' }); + + await new Promise(resolve => setTimeout(resolve, 20)); + + // Only the valid notification should be processed + expect(receivedNotifications.length).toBe(1); + expect(receivedNotifications[0].method).toBe('valid'); + }); + + test('handles empty response lines gracefully', async () => { + subprocess.registerHandler('test', () => 'works'); + + // Send empty lines + subprocess.pushEmptyLine(); + subprocess.pushEmptyLine(); + + // Valid request should still work + const result = await client.request('test')(); + + expect(E.isRight(result)).toBe(true); + if (E.isRight(result)) { + expect(result.right).toBe('works'); + } + }); + + test('handles response with wrong JSON-RPC version gracefully', async () => { + const receivedNotifications: JsonRpcNotification[] = []; + client.onNotification(notification => { + receivedNotifications.push(notification); + }); + + // Send response with wrong version (still valid JSON) + subprocess.sendMalformedResponse('{"jsonrpc":"1.0","method":"wrongVersion"}'); + + // The client may or may not process this depending on implementation + // But it should not crash + subprocess.sendNotification('afterWrongVersion', {}); + + await new Promise(resolve => setTimeout(resolve, 20)); + + // At minimum, subsequent valid messages should work + expect(receivedNotifications.length).toBeGreaterThanOrEqual(1); + }); + }); + + describe('End-to-End Scenarios', () => { + test('simulates initialize -> session/new -> session/prompt flow', async () => { + // Register handlers simulating real ACP protocol + subprocess.registerHandler('initialize', () => ({ + protocolVersion: '1.0', + serverInfo: { name: 'goose-acp', version: '1.16.0' }, + })); + + subprocess.registerHandler('session/new', () => ({ + sessionId: 'test-session-123', + })); + + subprocess.registerHandler('session/prompt', params => { + const p = params as { prompt: string }; + return { + response: `Echo: ${p.prompt}`, + }; + }); + + // Simulate ACP initialization flow + const initResult = await client.request<{ + protocolVersion: string; + serverInfo: { name: string; version: string }; + }>('initialize')(); + + expect(E.isRight(initResult)).toBe(true); + if (E.isRight(initResult)) { + expect(initResult.right.protocolVersion).toBe('1.0'); + expect(initResult.right.serverInfo.name).toBe('goose-acp'); + } + + const sessionResult = await client.request<{ sessionId: string }>('session/new')(); + expect(E.isRight(sessionResult)).toBe(true); + if (E.isRight(sessionResult)) { + expect(sessionResult.right.sessionId).toBe('test-session-123'); + } + + const promptResult = await client.request<{ response: string }>('session/prompt', { + prompt: 'Hello, Goose!', + })(); + expect(E.isRight(promptResult)).toBe(true); + if (E.isRight(promptResult)) { + expect(promptResult.right.response).toBe('Echo: Hello, Goose!'); + } + }); + + test('handles streaming updates via notifications', async () => { + subprocess.registerHandler('session/prompt', () => { + // Simulate sending streaming updates as notifications + setTimeout(() => subprocess.sendNotification('session/update', { chunk: 'Hello' }), 5); + setTimeout(() => subprocess.sendNotification('session/update', { chunk: ' World' }), 10); + setTimeout(() => subprocess.sendNotification('session/update', { done: true }), 15); + return { started: true }; + }); + + const updates: unknown[] = []; + client.onNotification(notification => { + if (notification.method === 'session/update') { + updates.push(notification.params); + } + }); + + const result = await client.request('session/prompt', { prompt: 'test' })(); + expect(E.isRight(result)).toBe(true); + + // Wait for notifications + await new Promise(resolve => setTimeout(resolve, 50)); + + expect(updates.length).toBe(3); + expect(updates[0]).toEqual({ chunk: 'Hello' }); + expect(updates[1]).toEqual({ chunk: ' World' }); + expect(updates[2]).toEqual({ done: true }); + }); + }); +}); diff --git a/src/extension/subprocessManager.ts b/src/extension/subprocessManager.ts new file mode 100644 index 0000000..e618995 --- /dev/null +++ b/src/extension/subprocessManager.ts @@ -0,0 +1,247 @@ +/** + * Subprocess manager for the goose acp process. + * Handles spawning, lifecycle events, and graceful shutdown. + */ + +import { ChildProcess, spawn } from 'child_process'; +import * as E from 'fp-ts/Either'; +import * as TE from 'fp-ts/TaskEither'; +import { + createSubprocessCrashError, + createSubprocessSpawnError, + SubprocessCrashError, + SubprocessSpawnError, +} from '../shared/errors'; +import { ProcessStatus } from '../shared/types'; +import { createJsonRpcClient, JsonRpcClient } from './jsonRpcClient'; +import { Logger } from './logger'; + +const GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5000; + +/** Configuration for creating a subprocess manager */ +export interface SubprocessManagerConfig { + readonly logger: Logger; + readonly workingDirectory: string; +} + +/** Status change callback type */ +export type StatusChangeCallback = (status: ProcessStatus) => void; + +/** Subprocess manager interface */ +export interface SubprocessManager { + readonly getStatus: () => ProcessStatus; + readonly start: (binaryPath: string) => TE.TaskEither; + readonly stop: () => TE.TaskEither; + readonly getClient: () => E.Either; + readonly onStatusChange: (callback: StatusChangeCallback) => void; +} + +/** Create a subprocess manager */ +export function createSubprocessManager(config: SubprocessManagerConfig): SubprocessManager { + const { logger, workingDirectory } = config; + + let status: ProcessStatus = ProcessStatus.STOPPED; + let process: ChildProcess | null = null; + let client: JsonRpcClient | null = null; + let lastError: SubprocessCrashError | null = null; + const statusChangeCallbacks: StatusChangeCallback[] = []; + + const setStatus = (newStatus: ProcessStatus): void => { + if (status !== newStatus) { + status = newStatus; + logger.info(`Status changed: ${newStatus}`); + for (const callback of statusChangeCallbacks) { + try { + callback(newStatus); + } catch (err) { + logger.error('Status change callback error:', err); + } + } + } + }; + + const getStatus = (): ProcessStatus => status; + + const start = (binaryPath: string): TE.TaskEither => { + return () => + new Promise(resolve => { + if (process !== null) { + logger.warn('Subprocess already running, stopping first'); + stop()().then(() => { + doStart(binaryPath, resolve); + }); + } else { + doStart(binaryPath, resolve); + } + }); + }; + + const doStart = ( + binaryPath: string, + resolve: (result: E.Either) => void + ): void => { + setStatus(ProcessStatus.STARTING); + lastError = null; + + logger.info(`Spawning: ${binaryPath} acp`); + logger.debug(`Working directory: ${workingDirectory}`); + + try { + process = spawn(binaryPath, ['acp'], { + stdio: ['pipe', 'pipe', 'pipe'], + cwd: workingDirectory, + env: globalThis.process.env, + }); + } catch (err) { + const error = err as NodeJS.ErrnoException; + setStatus(ProcessStatus.ERROR); + resolve( + E.left(createSubprocessSpawnError(binaryPath, error.code ?? 'UNKNOWN', error.errno ?? -1)) + ); + return; + } + + let hasResolved = false; + + const onSpawnError = (err: NodeJS.ErrnoException): void => { + logger.error('Spawn error:', err.message); + setStatus(ProcessStatus.ERROR); + + if (!hasResolved) { + hasResolved = true; + resolve( + E.left(createSubprocessSpawnError(binaryPath, err.code ?? 'UNKNOWN', err.errno ?? -1)) + ); + } + }; + + const onExit = (code: number | null, signal: string | null): void => { + logger.info(`Process exited: code=${code}, signal=${signal}`); + + if (client) { + client.dispose(); + client = null; + } + + process = null; + + if (status === ProcessStatus.RUNNING) { + lastError = createSubprocessCrashError(code, signal); + setStatus(ProcessStatus.ERROR); + } else { + setStatus(ProcessStatus.STOPPED); + } + }; + + process.on('error', onSpawnError); + process.on('exit', onExit); + + if (process.stderr) { + process.stderr.on('data', (data: Buffer) => { + const lines = data.toString('utf8').trim().split('\n'); + for (const line of lines) { + if (line) { + logger.warn(`[stderr] ${line}`); + } + } + }); + } + + if (process.stdin && process.stdout) { + client = createJsonRpcClient({ + stdin: process.stdin, + stdout: process.stdout, + logger: logger.child('JsonRpc'), + timeoutMs: 30000, + }); + + setStatus(ProcessStatus.RUNNING); + logger.info(`Subprocess started: pid=${process.pid}`); + + if (!hasResolved) { + hasResolved = true; + resolve(E.right(undefined)); + } + } else { + logger.error('Failed to get stdin/stdout from process'); + setStatus(ProcessStatus.ERROR); + + if (!hasResolved) { + hasResolved = true; + resolve(E.left(createSubprocessSpawnError(binaryPath, 'NO_STDIO', -1))); + } + } + }; + + const stop = (): TE.TaskEither => { + return () => + new Promise(resolve => { + if (process === null) { + logger.debug('No process to stop'); + resolve(E.right(undefined)); + return; + } + + logger.info('Stopping subprocess...'); + + if (client) { + client.dispose(); + client = null; + } + + const currentProcess = process; + let killed = false; + + const forceKillTimer = setTimeout(() => { + if (!killed && currentProcess.pid) { + logger.warn('Graceful shutdown timeout, sending SIGKILL'); + try { + currentProcess.kill('SIGKILL'); + } catch { + // Process may have already exited + } + } + }, GRACEFUL_SHUTDOWN_TIMEOUT_MS); + + const cleanup = (): void => { + killed = true; + clearTimeout(forceKillTimer); + process = null; + setStatus(ProcessStatus.STOPPED); + resolve(E.right(undefined)); + }; + + currentProcess.once('exit', cleanup); + + try { + currentProcess.kill('SIGTERM'); + } catch { + cleanup(); + } + }); + }; + + const getClient = (): E.Either => { + if (client && status === ProcessStatus.RUNNING) { + return E.right(client); + } + + if (lastError) { + return E.left(lastError); + } + + return E.left(createSubprocessCrashError(null, null)); + }; + + const onStatusChange = (callback: StatusChangeCallback): void => { + statusChangeCallbacks.push(callback); + }; + + return { + getStatus, + start, + stop, + getClient, + onStatusChange, + }; +} diff --git a/src/extension/versionChecker.test.ts b/src/extension/versionChecker.test.ts new file mode 100644 index 0000000..a01319d --- /dev/null +++ b/src/extension/versionChecker.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, test } from 'bun:test'; +import { + compareVersions, + MINIMUM_VERSION, + meetsMinimumVersion, + parseVersion, +} from './versionChecker'; + +describe('parseVersion', () => { + test('parses simple version', () => { + expect(parseVersion('1.16.0')).toBe('1.16.0'); + }); + + test('parses version with v prefix', () => { + expect(parseVersion('v1.16.0')).toBe('1.16.0'); + }); + + test('parses version with goose prefix', () => { + expect(parseVersion('goose 1.16.0')).toBe('1.16.0'); + }); + + test('parses version with "goose version" prefix', () => { + expect(parseVersion('goose version 1.16.0')).toBe('1.16.0'); + }); + + test('parses version with pre-release suffix', () => { + expect(parseVersion('Goose 1.16.0-beta')).toBe('1.16.0'); + }); + + test('parses two-segment version', () => { + expect(parseVersion('1.16')).toBe('1.16'); + }); + + test('handles whitespace', () => { + expect(parseVersion(' 1.16.0 \n')).toBe('1.16.0'); + }); + + test('returns null for empty string', () => { + expect(parseVersion('')).toBe(null); + }); + + test('returns null for whitespace only', () => { + expect(parseVersion(' ')).toBe(null); + }); + + test('returns null for invalid version', () => { + expect(parseVersion('not a version')).toBe(null); + }); +}); + +describe('compareVersions', () => { + test('equal versions return 0', () => { + expect(compareVersions('1.16.0', '1.16.0')).toBe(0); + }); + + test('major version difference', () => { + expect(compareVersions('2.0.0', '1.0.0')).toBeGreaterThan(0); + expect(compareVersions('1.0.0', '2.0.0')).toBeLessThan(0); + }); + + test('minor version difference', () => { + expect(compareVersions('1.17.0', '1.16.0')).toBeGreaterThan(0); + expect(compareVersions('1.16.0', '1.17.0')).toBeLessThan(0); + }); + + test('patch version difference', () => { + expect(compareVersions('1.16.1', '1.16.0')).toBeGreaterThan(0); + expect(compareVersions('1.16.0', '1.16.1')).toBeLessThan(0); + }); + + test('handles two-segment versions', () => { + expect(compareVersions('1.16', '1.16.0')).toBe(0); + expect(compareVersions('1.16', '1.15.9')).toBeGreaterThan(0); + }); +}); + +describe('meetsMinimumVersion', () => { + test('exact minimum version is compatible', () => { + expect(meetsMinimumVersion('1.16.0', '1.16.0')).toBe(true); + }); + + test('higher patch version is compatible', () => { + expect(meetsMinimumVersion('1.16.1', '1.16.0')).toBe(true); + }); + + test('higher minor version is compatible', () => { + expect(meetsMinimumVersion('1.17.0', '1.16.0')).toBe(true); + }); + + test('higher major version is compatible', () => { + expect(meetsMinimumVersion('2.0.0', '1.16.0')).toBe(true); + }); + + test('lower version is incompatible', () => { + expect(meetsMinimumVersion('1.15.9', '1.16.0')).toBe(false); + expect(meetsMinimumVersion('1.0.0', '1.16.0')).toBe(false); + expect(meetsMinimumVersion('0.99.0', '1.16.0')).toBe(false); + }); +}); + +describe('MINIMUM_VERSION', () => { + test('is set to 1.16.0', () => { + expect(MINIMUM_VERSION).toBe('1.16.0'); + }); +}); diff --git a/src/extension/versionChecker.ts b/src/extension/versionChecker.ts new file mode 100644 index 0000000..5885383 --- /dev/null +++ b/src/extension/versionChecker.ts @@ -0,0 +1,181 @@ +/** + * Version checking for Goose binary compatibility. + * Validates installed version meets minimum requirements. + */ + +import { spawn } from 'child_process'; +import * as E from 'fp-ts/Either'; +import * as TE from 'fp-ts/TaskEither'; +import { createVersionMismatchError, VersionMismatchError } from '../shared/errors'; + +export const MINIMUM_VERSION = '1.16.0'; +const VERSION_CHECK_TIMEOUT_MS = 5000; + +export interface VersionCheckResult { + readonly version: string; + readonly isCompatible: boolean; +} + +/** + * Parse version string from goose --version output. + * Handles various formats: + * - "goose 1.16.0" + * - "1.16.0" + * - "v1.16.0" + * - "goose version 1.16.0" + * - "Goose 1.16.0-beta" + * + * @param output - Raw output from goose --version command + * @returns Parsed version string (e.g., "1.16.0") or null if parsing fails + */ +export function parseVersion(output: string): string | null { + const trimmed = output.trim(); + if (!trimmed) { + return null; + } + + // Match semantic version pattern: major.minor.patch (with optional pre-release suffix) + // The regex captures the core version numbers before any pre-release suffix like -beta + const versionRegex = /v?(\d+\.\d+(?:\.\d+)?)/i; + const match = trimmed.match(versionRegex); + + if (match && match[1]) { + return match[1]; + } + + return null; +} + +/** + * Parse a version string into numeric segments. + * Handles versions with 2 or 3 segments (e.g., "1.16" or "1.16.0"). + * + * @param version - Semantic version string + * @returns Array of numeric segments [major, minor, patch] + */ +function parseVersionSegments(version: string): readonly [number, number, number] { + const parts = version.split('.').map(part => parseInt(part, 10) || 0); + return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0]; +} + +/** + * Compare two semantic versions. + * + * @param a - First version string + * @param b - Second version string + * @returns Negative if a < b, positive if a > b, zero if equal + */ +export function compareVersions(a: string, b: string): number { + const [aMajor, aMinor, aPatch] = parseVersionSegments(a); + const [bMajor, bMinor, bPatch] = parseVersionSegments(b); + + if (aMajor !== bMajor) { + return aMajor - bMajor; + } + if (aMinor !== bMinor) { + return aMinor - bMinor; + } + return aPatch - bPatch; +} + +/** + * Check if a version meets minimum requirements. + * + * @param version - Version to check + * @param minimum - Minimum required version + * @returns True if version >= minimum + */ +export function meetsMinimumVersion(version: string, minimum: string): boolean { + return compareVersions(version, minimum) >= 0; +} + +/** + * Check the Goose binary version by executing `goose --version`. + * + * @param binaryPath - Path to the goose binary + * @returns TaskEither containing VersionCheckResult on success, or VersionMismatchError on failure + */ +export function checkVersion( + binaryPath: string +): TE.TaskEither { + return () => + new Promise(resolve => { + let stdout = ''; + let stderr = ''; + let resolved = false; + let childProcess: ReturnType | null = null; + + const cleanup = (): void => { + if (childProcess && !childProcess.killed) { + try { + childProcess.kill('SIGTERM'); + } catch { + // Process may have already exited + } + } + }; + + const resolveWith = (result: E.Either): void => { + if (!resolved) { + resolved = true; + cleanup(); + resolve(result); + } + }; + + const timeoutId = setTimeout(() => { + resolveWith(E.left(createVersionMismatchError('unknown', MINIMUM_VERSION))); + }, VERSION_CHECK_TIMEOUT_MS); + + try { + childProcess = spawn(binaryPath, ['--version'], { + stdio: ['ignore', 'pipe', 'pipe'], + timeout: VERSION_CHECK_TIMEOUT_MS, + }); + } catch { + clearTimeout(timeoutId); + resolveWith(E.left(createVersionMismatchError('unknown', MINIMUM_VERSION))); + return; + } + + childProcess.stdout?.on('data', (data: Buffer) => { + stdout += data.toString('utf8'); + }); + + childProcess.stderr?.on('data', (data: Buffer) => { + stderr += data.toString('utf8'); + }); + + childProcess.on('error', () => { + clearTimeout(timeoutId); + resolveWith(E.left(createVersionMismatchError('unknown', MINIMUM_VERSION))); + }); + + childProcess.on('close', (_code: number | null) => { + clearTimeout(timeoutId); + + // Try to parse version from stdout first, then stderr + const output = stdout || stderr; + const version = parseVersion(output); + + if (version === null) { + // Parse failure - treat as incompatible + resolveWith(E.left(createVersionMismatchError('unknown', MINIMUM_VERSION))); + return; + } + + const isCompatible = meetsMinimumVersion(version, MINIMUM_VERSION); + + if (isCompatible) { + resolveWith( + E.right({ + version, + isCompatible: true, + }) + ); + } else { + resolveWith(E.left(createVersionMismatchError(version, MINIMUM_VERSION))); + } + }); + }); +} diff --git a/src/extension/webviewProvider.ts b/src/extension/webviewProvider.ts new file mode 100644 index 0000000..b901a41 --- /dev/null +++ b/src/extension/webviewProvider.ts @@ -0,0 +1,225 @@ +/** + * WebviewViewProvider for the Goose chat panel. + * Manages webview lifecycle, message queue, and HTML generation with CSP. + */ + +import * as vscode from 'vscode'; +import { + AnyWebviewMessage, + createStatusUpdateMessage, + createVersionStatusMessage, + isWebviewReadyMessage, + VersionStatusPayload, +} from '../shared/messages'; +import { ProcessStatus } from '../shared/types'; +import { Logger } from './logger'; + +/** Configuration for creating a webview provider */ +export interface WebviewProviderConfig { + readonly extensionUri: vscode.Uri; + readonly logger: Logger; +} + +/** Message callback type */ +export type MessageCallback = (message: AnyWebviewMessage) => void; + +/** Extended WebviewViewProvider interface with message methods */ +export interface WebviewProvider extends vscode.WebviewViewProvider { + readonly postMessage: (message: AnyWebviewMessage) => void; + readonly onMessage: (callback: MessageCallback) => vscode.Disposable; + readonly updateStatus: (status: ProcessStatus) => void; + readonly updateVersionStatus: (payload: VersionStatusPayload) => void; + readonly waitForReady: () => Promise; +} + +/** Create a webview provider */ +export function createWebviewProvider(config: WebviewProviderConfig): WebviewProvider { + const { extensionUri, logger } = config; + + let view: vscode.WebviewView | null = null; + let isReady = false; + const messageQueue: AnyWebviewMessage[] = []; + const messageCallbacks: MessageCallback[] = []; + const readyCallbacks: (() => void)[] = []; + + // Track last known state to re-send on webview reconnect + let lastStatus: ProcessStatus | null = null; + let lastVersionStatus: VersionStatusPayload | null = null; + + const flushQueue = (): void => { + if (!view || !isReady) return; + + logger.debug(`Flushing ${messageQueue.length} queued messages`); + while (messageQueue.length > 0) { + const message = messageQueue.shift(); + if (message) { + view.webview.postMessage(message); + } + } + }; + + const postMessage = (message: AnyWebviewMessage): void => { + if (!view) { + logger.debug('Webview not available, queueing message'); + messageQueue.push(message); + return; + } + + if (!isReady) { + logger.debug(`Queueing message: ${message.type}`); + messageQueue.push(message); + return; + } + + view.webview.postMessage(message); + }; + + const onMessage = (callback: MessageCallback): vscode.Disposable => { + messageCallbacks.push(callback); + return { + dispose: () => { + const index = messageCallbacks.indexOf(callback); + if (index > -1) { + messageCallbacks.splice(index, 1); + } + }, + }; + }; + + const updateStatus = (status: ProcessStatus): void => { + lastStatus = status; + postMessage(createStatusUpdateMessage(status)); + }; + + const updateVersionStatus = (payload: VersionStatusPayload): void => { + lastVersionStatus = payload; + postMessage( + createVersionStatusMessage(payload.status, payload.minimumVersion, { + detectedVersion: payload.detectedVersion, + installUrl: payload.installUrl, + updateUrl: payload.updateUrl, + }) + ); + }; + + const resendState = (): void => { + // Re-send last known status when webview reconnects + if (lastVersionStatus) { + logger.debug('Re-sending version status to reconnected webview'); + postMessage( + createVersionStatusMessage(lastVersionStatus.status, lastVersionStatus.minimumVersion, { + detectedVersion: lastVersionStatus.detectedVersion, + installUrl: lastVersionStatus.installUrl, + updateUrl: lastVersionStatus.updateUrl, + }) + ); + } else if (lastStatus) { + logger.debug('Re-sending process status to reconnected webview'); + postMessage(createStatusUpdateMessage(lastStatus)); + } + }; + + const handleMessage = (message: unknown): void => { + logger.debug('Received message from webview:', message); + + if (isWebviewReadyMessage(message)) { + logger.info('Webview ready signal received'); + isReady = true; + flushQueue(); + resendState(); + // Resolve any pending waitForReady promises + while (readyCallbacks.length > 0) { + const cb = readyCallbacks.shift(); + cb?.(); + } + return; + } + + for (const callback of messageCallbacks) { + try { + callback(message as AnyWebviewMessage); + } catch (err) { + logger.error('Message callback error:', err); + } + } + }; + + const waitForReady = (): Promise => { + if (isReady) { + return Promise.resolve(); + } + return new Promise(resolve => { + readyCallbacks.push(resolve); + }); + }; + + const getNonce = (): string => { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; + }; + + const getWebviewContent = (webview: vscode.Webview): string => { + const nonce = getNonce(); + + const scriptUri = webview.asWebviewUri( + vscode.Uri.joinPath(extensionUri, 'dist', 'webview', 'main.js') + ); + const styleUri = webview.asWebviewUri( + vscode.Uri.joinPath(extensionUri, 'dist', 'webview', 'styles.css') + ); + + return ` + + + + + + + Goose + + +
+ + +`; + }; + + const resolveWebviewView = ( + webviewView: vscode.WebviewView, + _context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken + ): void => { + view = webviewView; + isReady = false; + + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist', 'webview')], + }; + + webviewView.webview.html = getWebviewContent(webviewView.webview); + + webviewView.webview.onDidReceiveMessage(handleMessage); + + webviewView.onDidDispose(() => { + logger.debug('Webview disposed'); + view = null; + isReady = false; + }); + + logger.info('Webview view resolved'); + }; + + return { + resolveWebviewView, + postMessage, + onMessage, + updateStatus, + updateVersionStatus, + waitForReady, + }; +} diff --git a/src/server/apiClient.ts b/src/server/apiClient.ts deleted file mode 100644 index 2f34b78..0000000 --- a/src/server/apiClient.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { EventEmitter } from "events"; -import { Message } from "../types/messages"; -import { logger as singletonLogger, Logger } from "../utils/logger"; // Import singleton logger -import { GooseConfig } from "../utils/configReader"; // Import GooseConfig from correct file -import * as vscode from 'vscode'; // Needed for getConfiguration - -// Define interfaces for complex parameter objects -interface StreamChatParams { - prompt: Message[]; // Expect an array of messages - abortController?: AbortController; // Make optional if sometimes omitted - sessionId?: string; // Make optional - workspaceDirectory?: string; // Make optional - // Add any other relevant parameters needed by the API -} - -/** - * Configuration for the ApiClient - */ -export interface ApiClientConfig { - baseUrl: string; - secretKey: string; - logger: Logger; // Use our specific Logger type - events?: EventEmitter; - debug?: boolean; -} - -/** - * A platform-agnostic client for communicating with the Goose API server - */ -export class ApiClient { - private baseUrl: string; - private secretKey: string; - private logger: Logger; // Store the specific Logger instance - private events: EventEmitter; - private secretProviderKeys: string[] = []; // Store names of secret keys - private debug: boolean; // Declare the debug property - - constructor(config: ApiClientConfig) { - this.baseUrl = config.baseUrl; - this.secretKey = config.secretKey; - this.events = config.events || new EventEmitter(); - this.logger = config.logger.createSource('ApiClient'); // Create a source-tagged logger - this.debug = config.debug ?? false; - } - - /** - * Sets the list of provider configuration keys that are considered secret. - * @param keys An array of secret key names. - */ - public setSecretProviderKeys(keys: string[]): void { - this.secretProviderKeys = keys; - this.logger.debug(`Received ${keys.length} secret provider keys to redact.`); - } - - /** - * Sets the list of secret keys specific to the current provider for redaction. - * @param keys An array of secret key names. - */ - public setSecretProviderKeysForCurrentProvider(keys: string[]): void { - // TODO: Implement logic to use these keys for redaction in the logger or request method. - this.logger.warn(`ApiClient.setSecretProviderKeys received keys: [${keys.join(', ')}], but redaction logic is not fully implemented.`); - } - - /** - * Redacts sensitive information (X-Secret-Key, provider secrets) from headers or body. - * @param data The data to redact (string or object). - * @returns The redacted data. - */ - private redactSecrets(data: any): any { - const redactedPlaceholder = '***REDACTED***'; - let redactedData = JSON.parse(JSON.stringify(data)); // Deep clone to avoid modifying original - - // Redact X-Secret-Key in headers (if data is an object) - if (typeof redactedData === 'object' && redactedData !== null && redactedData['X-Secret-Key']) { - redactedData['X-Secret-Key'] = redactedPlaceholder; - } - - // If data is a string, attempt simple string replacement - // This is less robust than object property redaction - if (typeof redactedData === 'string') { - // Redact X-Secret-Key value if it appears in the string body - // Assuming the header format might be logged directly in error scenarios - const secretKeyPattern = new RegExp(`"?X-Secret-Key"?:\s*"?${this.secretKey}"?`, 'gi'); - redactedData = redactedData.replace(secretKeyPattern, '"X-Secret-Key":"' + redactedPlaceholder + '"'); - - // Add generic redaction for common API key patterns in stringified data - const commonKeyPatterns = [ - /"api_key"\s*:\s*"(.*?)"/gi, - /"secret_key"\s*:\s*"(.*?)"/gi, - /"token"\s*:\s*"(.*?)"/gi, - /api_key=([^&\s]+)/gi, - /secret_key=([^&\s]+)/gi, - /token=([^&\s]+)/gi - ]; - - for (const pattern of commonKeyPatterns) { - redactedData = redactedData.replace(pattern, (match: string, p1: string) => { - // Replace the value part (p1) with the placeholder - return match.replace(p1, redactedPlaceholder); - }); - } - } - - return redactedData; - } - - /** - * Make a request to the Goose API - * @param path The API endpoint path - * @param options Fetch options - * @returns The fetch response - */ - public async request( - path: string, - options: RequestInit = {}, - ): Promise { - const url = `${this.baseUrl}${path}`; - const headers = { - ...options.headers, - "Content-Type": "application/json", - "X-Secret-Key": this.secretKey, - }; - - // Read logging configuration - const config = vscode.workspace.getConfiguration('goose.logging'); - const logSensitive = config.get('logSensitiveRequests', false); - // Logging happens only if logger is enabled and level is appropriate (handled by logger itself) - // We only need the logSensitive flag here for conditional body logging. - - this.logger.debug(`API Request: ${options.method || 'GET'} ${path}`); - - // Log redacted headers at DEBUG level - this.logger.debug('Request Headers:', this.redactSecrets(headers)); - - // Log redacted body conditionally at DEBUG level - if (options.body && logSensitive) { - try { - // Attempt to parse body if JSON, otherwise log as string - let bodyToLog: any; - if (typeof options.body === 'string') { - try { - bodyToLog = JSON.parse(options.body); - } catch (e) { - bodyToLog = options.body; // Log as string if not JSON - } - } else { - bodyToLog = options.body; - } - this.logger.debug('Request Body:', this.redactSecrets(bodyToLog)); - } catch (e) { - this.logger.warn('Could not process request body for logging:', e); - this.logger.debug('Raw Request Body (unredacted, use with caution):', options.body); - } - } else if (options.body) { - this.logger.debug('Request Body: [REDACTED BY CONFIG]'); - } - - try { - const response = await fetch(url, { ...options, headers }); - - this.logger.debug(`API Response Status: ${response.status} ${response.statusText} for ${options.method || 'GET'} ${path}`); - - // Log response headers at DEBUG level (generally safe) - const responseHeaders: { [key: string]: string } = {}; - response.headers.forEach((value, key) => { - responseHeaders[key] = value; - }); - this.logger.debug('Response Headers:', responseHeaders); - - if (!response.ok) { - let errorBody = "[Could not read error body]"; - try { - errorBody = await response.text(); - } catch (e) { /* Ignore */ } - - let errorBodyToShow = logSensitive ? this.redactSecrets(errorBody) : "[REDACTED BY CONFIG]"; - this.logger.error(`API Error ${response.status} ${response.statusText} for ${options.method || 'GET'} ${path}. Body:`, errorBodyToShow); - - throw new Error( - `API request failed: ${response.status} ${response.statusText} - ${errorBody}` // Include body - ); - } - - // Log response body conditionally at DEBUG level - if (logSensitive) { - const clone = response.clone(); - try { - let bodyToLog: any; - const contentType = clone.headers.get('content-type'); - if (contentType && contentType.includes('application/json')) { - bodyToLog = await clone.json(); - } else { - bodyToLog = await clone.text(); - } - this.logger.debug('Response Body:', this.redactSecrets(bodyToLog)); - } catch (e) { - this.logger.warn('Could not process response body for logging:', e); - // Avoid logging raw potentially sensitive body here - } - } else { - // Only log body if sensitive logging is off *and* it's not a streaming type? - // Or just always say redacted if sensitive logging is off? - // Let's go with always redacted if sensitive logging is off. - this.logger.debug('Response Body: [REDACTED BY CONFIG]'); - } - - return response; - } catch (error) { - // Log the error object itself which might contain more details - // The error from response.ok check is already logged above. - if (!(error instanceof Error && error.message.startsWith('API request failed'))) { - this.logger.error(`API request to ${path} encountered an unexpected error:`, error); - } - throw error; - } - } - - // --- START: Implemented Session Methods --- - - public async listSessions(): Promise { - this.logger.info("Fetching sessions list..."); - const path = '/sessions'; - const options: RequestInit = { method: 'GET' }; - try { - const response = await this.request(path, options); - const rawData = await response.json(); - // this.logger.debug('Raw response from /sessions:', JSON.stringify(rawData)); // Reverted - // Handle potential response structures (direct array or object with 'sessions' key) - const sessions = Array.isArray(rawData) ? rawData : (rawData?.sessions || []); - this.logger.info(`Sessions list fetched successfully. Processed count: ${sessions.length}`); - // TODO: Consider adding data validation/transformation if needed - return sessions; - } catch (error) { - this.logger.error('Failed to list sessions:', error); - throw error; // Re-throw to allow caller to handle - } - } - - public async getSessionHistory(sessionId: string): Promise { - this.logger.info(`Fetching session history for ID: ${sessionId}`); - const path = `/sessions/${sessionId}`; // Use path parameter - const options: RequestInit = { method: 'GET' }; - try { - const response = await this.request(path, options); - const data = await response.json(); - this.logger.info(`Session history fetched successfully for ID: ${sessionId}`); - // TODO: Consider adding data validation/transformation if needed - return data; - } catch (error) { - this.logger.error(`Failed to get session history for ${sessionId}:`, error); - // Decide whether to return null or throw based on expected caller behavior - // Throwing might be better to signal a clear failure - throw error; - } - } - - // --- END: Implemented Session Methods --- - - // --- START: Stub methods to fix TS2339 errors --- - - public async renameSession(sessionId: string, newName: string): Promise { - this.logger.warn(`ApiClient.renameSession(${sessionId}, ${newName}) is not implemented`); - return false; // Placeholder - } - - public async deleteSession(sessionId: string): Promise { - this.logger.warn(`ApiClient.deleteSession(${sessionId}) is not implemented`); - return false; // Placeholder - } - - public async getAgentVersions(): Promise<{ available_versions: string[], default_version: string }> { - this.logger.info("Fetching agent versions..."); - const path = '/agent/versions'; - const options: RequestInit = { method: 'GET' }; - try { - const response = await this.request(path, options); - const responseData = await response.json(); - this.logger.info(`Agent versions fetched successfully. Default: ${responseData.default_version}`, responseData); - return responseData; - } catch (error) { - this.logger.error('Failed to fetch agent versions:', error); - throw error; // Re-throw to allow caller to handle - } - } - - public async createAgent(provider: string, model?: string, version?: string): Promise { - this.logger.info(`Updating agent provider/model with: provider=${provider}, model=${model || "default"}, version=${version || "default"}`); - const path = '/agent/update_provider'; // Correct endpoint from main branch - const body: { provider: string; model?: string; version?: string } = { provider }; - if (model) { body.model = model; } - if (version) { body.version = version; } // Keep version for now - - const options: RequestInit = { - method: 'POST', - body: JSON.stringify(body) - }; - - try { - const response = await this.request(path, options); - - // Handle potentially empty/non-JSON success responses (from main branch logic) - try { - const contentLength = response.headers.get('content-length'); - if (contentLength === '0') { - this.logger.info(`Agent provider/model updated successfully (Status ${response.status}, Content-Length: 0).`); - return { success: true }; - } - // If Content-Length is not '0' or not present, attempt to parse JSON - const responseData = await response.json(); - this.logger.info(`Agent provider/model updated (Status ${response.status}), response:`, responseData); - return responseData; - } catch (parseError) { - this.logger.error(`Agent provider/model update request succeeded (Status ${response.status}), but failed to parse JSON response:`, parseError); - const textBody = await response.text().catch(() => ""); // Attempt to get text body - this.logger.error(`Response body text (if any): ${textBody}`); - return { success: true, warning: "Response body was not valid JSON." }; - } - } catch (error) { - this.logger.error(`Failed to update agent provider/model for ${provider}:`, error); - throw error; // Re-throw to allow caller to handle - } - } - - public async setAgentPrompt(prompt: string): Promise { - this.logger.info(`Setting agent system prompt...`); - const path = '/agent/prompt'; - const trimmedPrompt = prompt.trim(); // Task 4.2: Trim the prompt - - // Task 4.3: Prevent empty system prompt configuration - if (trimmedPrompt === '') { - this.logger.info('Trimmed system prompt is empty. Skipping API call to /agent/prompt.'); - return Promise.resolve(undefined); - } - - const options: RequestInit = { - method: 'POST', - body: JSON.stringify({ extension: trimmedPrompt }) // Use trimmedPrompt - }; - try { - const response = await this.request(path, options); - const responseData = await response.json(); // Assuming success returns JSON - this.logger.info(`Agent prompt set successfully, response: ${JSON.stringify(responseData)}`); - return responseData; // Return the actual response data - } catch (error) { - this.logger.error(`API request to ${path} failed:`, error); - // Re-throw the error so the test can catch it - throw error; // Re-throw the original error from this.request - } - } - - public async addExtension(name: string, type: 'builtin' = 'builtin'): Promise { - this.logger.info(`Adding extension: ${name} (type: ${type})`); - const path = '/extensions/add'; - const body = { type, name }; - const options: RequestInit = { - method: 'POST', - body: JSON.stringify(body) - }; - try { - const response = await this.request(path, options); - const responseData = await response.json(); - this.logger.info(`Extension '${name}' added successfully. Response:`, responseData); - return responseData; - } catch (error) { - this.logger.error(`Failed to add extension '${name}':`, error); - throw error; // Re-throw to allow caller to handle - } - } - - public async streamChatResponse(params: StreamChatParams): Promise { - const { prompt: messages, abortController, sessionId, workspaceDirectory } = params; - // Always ensure we have a working directory - const effectiveWorkingDir = workspaceDirectory || process.cwd(); - - this.logger.info(`Streaming chat response with working dir: ${effectiveWorkingDir}, session: ${sessionId || 'new'}`); - - const path = "/reply"; - const options: RequestInit = { - method: "POST", - body: JSON.stringify({ - messages, - session_id: sessionId, - session_working_dir: effectiveWorkingDir, - }), - signal: abortController?.signal, - headers: { - Accept: "text/event-stream", // Crucial for streaming - // Content-Type and X-Secret-Key are added by this.request - }, - // IMPORTANT: Keepalive is often needed for long-running streams, - // but node-fetch might handle this differently or it might not be needed for localhost. - // If streams disconnect prematurely, consider adding: keepalive: true - }; - - // Use the base request method, which handles headers, logging, and basic error checking. - // The response object itself will contain the stream. - try { - const response = await this.request(path, options); - // The caller is responsible for reading the stream from response.body - this.logger.info(`Stream request initiated successfully for ${path}`); - return response; - } catch (error) { - this.logger.error(`Failed to initiate stream request to ${path}:`, error); - throw error; // Re-throw to allow caller to handle - } - - // --- Old Fake Response Logic Removed --- - // async function* emptyGenerator(): AsyncIterable {} - } - - // --- END: Stub methods --- - - /** - * Make a request to the Goose API - * @param path The API endpoint path - * @param options Fetch options - * @returns The fetch response - */ - // ... rest of your code remains the same ... -} diff --git a/src/server/chat/chatProcessor.ts b/src/server/chat/chatProcessor.ts deleted file mode 100644 index f9ba893..0000000 --- a/src/server/chat/chatProcessor.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { EventEmitter } from 'events'; -import { TextDecoder } from 'util'; -import { ServerManager } from '../serverManager'; -import { Message, MessageContent, TextPart, CodeContextPart } from '../../types'; // Updated imports -import * as vscode from 'vscode'; -import { CodeReference } from '../../utils/codeReferenceManager'; // Added import -import { SessionManager, SessionEvents } from './sessionManager'; -import { logger } from '../../utils/logger'; - -/** - * Events emitted by the chat processor - */ -export enum ChatEvents { - MESSAGE_RECEIVED = 'messageReceived', - FINISH = 'finish', - ERROR = 'error' -} - -/** - * Event types for SSE stream - */ -type MessageEvent = - | { type: 'Message'; message: Message } - | { type: 'Error'; error: string } - | { type: 'Finish'; reason: string }; - -/** - * Formats a user message with optional code context into the new format. - * If codeContext is provided, returns the formatted string; otherwise, returns the userQuery as-is. - * @param userQuery The user's textual query. - * @param codeContext Optional CodeContextPart containing code reference info. - */ -export function formatMessageWithCodeContext( - userQuery: string, - codeContext?: CodeContextPart -): string { - if (!codeContext) { - return userQuery; - } - - const { filePath, fileName, selectedText, languageId } = codeContext; - const codeLines = selectedText ? selectedText.split('\n') : []; - const lineCount = codeLines.length; - - let codeBlock = ''; - if (lineCount < 100 && selectedText.trim() !== '') { - // Inline the code, indenting each line by two spaces - const indentedCode = codeLines.map(line => ` ${line}`).join('\n'); - codeBlock = ` \`${languageId || ''}\n${indentedCode}\n \`\n\n`; - } else { - // Instruct Goose to read the file - codeBlock = - ` \`${languageId || ''}\n` + - ` // File content for '${fileName}' (${lineCount} lines) is not displayed inline. Goose, please read the file at '${filePath}'.\n` + - ` \`\n\n`; - } - - return ` -'${filePath}' (see below for file content) - -${codeBlock}${userQuery} -`; -} - -/** -* Handles communication with the Goose server for chat functionality -*/ -export class ChatProcessor { - private serverManager: ServerManager; - private eventEmitter: EventEmitter; - private abortController: AbortController | null = null; - private currentMessages: Message[] = []; - private shouldStop: boolean = false; - private sessionManager: SessionManager | null = null; - - constructor(serverManager: ServerManager) { - this.serverManager = serverManager; - this.eventEmitter = new EventEmitter(); - } - - /** - * Set the session manager - */ - public setSessionManager(sessionManager: SessionManager): void { - this.sessionManager = sessionManager; - } - - /** - * Send a message to the Goose AI - */ - public async sendMessage( - text: string, - codeReferencesParam?: CodeReference[], - prependedCode?: CodeReference, - messageId?: string, - sessionId?: string - ): Promise { - const hasText = text && text.trim() !== ''; - const hasCodeReferences = codeReferencesParam && codeReferencesParam.length > 0; - const hasPrependedCode = !!prependedCode; - - if (!hasText && !hasCodeReferences && !hasPrependedCode) { - logger.info('ChatProcessor: sendMessage called with empty user text and no code context. Not proceeding.'); - return; - } - - let effectiveSessionId: string | undefined = sessionId; - - if (!effectiveSessionId && this.sessionManager) { - const currentSessionId = this.sessionManager.getCurrentSessionId(); - if (currentSessionId) { - effectiveSessionId = currentSessionId; - } - } - - // Determine code context: prefer prependedCode, else first codeReferencesParam, else undefined - let codeContext: CodeContextPart | undefined; - if (prependedCode && prependedCode.selectedText && prependedCode.selectedText.trim() !== '') { - codeContext = { ...prependedCode, type: 'code_context' }; - } else if (codeReferencesParam && codeReferencesParam.length > 0) { - const firstValid = codeReferencesParam.find( - ref => ref && typeof ref.selectedText === 'string' && ref.selectedText.trim() !== '' - ); - if (firstValid) { - codeContext = { ...firstValid, type: 'code_context' }; - } - } - - // Use the formatting utility to produce the message string - const formatted = formatMessageWithCodeContext(text ? text.trim() : '', codeContext); - - const userMessage: Message = { - id: messageId || `user_${Date.now()}`, - role: 'user', - created: Date.now(), - content: [ - { - type: 'text', - text: formatted - } - ] - }; - - this.currentMessages.push(userMessage); // This will be used for API serialization later - this.shouldStop = false; - - try { - const response = await this.sendChatRequest(effectiveSessionId); - - // If this was a new session, load the session info after sending the first message - if (!effectiveSessionId && this.sessionManager) { - await this.sessionManager.fetchSessions(); - } else if (effectiveSessionId && this.sessionManager) { - // Update existing session to remove isLocal flag after successful reply - await this.updateSessionAfterReply(effectiveSessionId); - } - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const aiMessageId = `ai_${Date.now()}`; - - // Ensure the response body is available - if (!response.body) { - throw new Error('Response body is null'); - } - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let accumulatedData = ''; - - const aiMessage: Message = { - id: aiMessageId, - role: 'assistant', - created: Date.now(), - content: [{ type: 'text', text: '' }] - }; - - this.currentMessages.push(aiMessage); - // this.emit(ChatEvents.MESSAGE_RECEIVED, { ...aiMessage }); // Placeholder emission - - while (true) { - if (this.shouldStop) { - logger.info("Stopping generation (shouldStop flag is true)"); - reader.cancel(); - this.emit(ChatEvents.FINISH, { ...aiMessage }, 'stopped'); - break; - } - - const { value, done } = await reader.read(); - - if (done) { - logger.info("Stream complete, emitting FINISH event"); - this.emit(ChatEvents.FINISH, { ...aiMessage }, 'complete'); - break; - } - - accumulatedData += decoder.decode(value, { stream: true }); - let newlineIndex; - - while ((newlineIndex = accumulatedData.indexOf('\n')) >= 0) { - const line = accumulatedData.substring(0, newlineIndex).trim(); - accumulatedData = accumulatedData.substring(newlineIndex + 1); - - if (line.startsWith('data:')) { - const jsonStr = line.substring(5).trim(); - if (jsonStr === '[DONE]') { - continue; - } - if (jsonStr) { - try { - const eventData = JSON.parse(jsonStr) as MessageEvent; - if (eventData.type === 'Message' && eventData.message) { - aiMessage.content = eventData.message.content; - aiMessage.created = Date.now(); - this.emit(ChatEvents.MESSAGE_RECEIVED, { ...aiMessage }); - } else if (eventData.type === 'Error') { - logger.error(`ChatProcessor: Stream error event: ${eventData.error}`); - this.emit(ChatEvents.ERROR, new Error(eventData.error)); - } - } catch (e) { - logger.error('ChatProcessor: Failed to parse SSE JSON line:', e, 'Problematic JSON string:', jsonStr); - } - } - } - } - } - } catch (error) { - this.shouldStop = true; - this.emit(ChatEvents.ERROR, error); - throw error; - } finally { - this.abortController = null; - } - } - - /** - * Stop the current generation - */ - public stopGeneration(): void { - if (this.abortController) { - this.abortController.abort(); - this.abortController = null; - } - this.shouldStop = true; - - if (this.currentMessages.length > 0) { - const lastMessage = this.currentMessages[this.currentMessages.length - 1]; - if (lastMessage.role === 'assistant') { - lastMessage.created = Date.now(); - } - } - - this.emit(ChatEvents.FINISH, null, 'aborted'); - } - - /** - * Get all current messages - */ - public getMessages(): Message[] { - return this.currentMessages; - } - - /** - * Clear all messages - */ - public clearMessages(): void { - this.currentMessages = []; - } - - /** - * Subscribe to chat events - */ - public on(event: ChatEvents, listener: (...args: any[]) => void): void { - this.eventEmitter.on(event, listener); - } - - /** - * Unsubscribe from chat events - */ - public off(event: ChatEvents, listener: (...args: any[]) => void): void { - this.eventEmitter.off(event, listener); - } - - /** - * Send a chat request to the server - */ - private serializeMessagesForApi(messages: Message[]): Message[] { - return messages.map(msg => { - // Only modify user messages for now, as assistant messages are already in the expected format. - // And system messages are not handled by this processor. - if (msg.role !== 'user') { - return msg; - } - - const serializedContent: MessageContent[] = []; - for (const part of msg.content) { - if (part.type === 'code_context') { - const codeCtxPart = part as CodeContextPart; - let codeToSend = codeCtxPart.selectedText; - const lineCount = codeCtxPart.selectedText.split('\n').length; - - if (lineCount > 100) { - // As per design: "truncated or replaced with a placeholder" - // Using a placeholder for clarity. - codeToSend = `[Code content >100 lines, see reference. Original selection was from ${codeCtxPart.fileName}:${codeCtxPart.startLine}-${codeCtxPart.endLine}]`; - logger.info(`ChatProcessor: CodeContextPart from ${codeCtxPart.filePath} has ${lineCount} lines. Truncating for API call.`); - } - - const serializedText = `// Meta: FilePath="${codeCtxPart.filePath}", LanguageId="${codeCtxPart.languageId}", Lines=${codeCtxPart.startLine}-${codeCtxPart.endLine}\n\`\`\`${codeCtxPart.languageId || ''}\n${codeToSend}\n\`\`\``; - serializedContent.push({ - type: 'text', - text: serializedText, - } as TextPart); - } else { - // TextPart, ImageContent, Tool parts are passed as is - serializedContent.push(part); - } - } - return { ...msg, content: serializedContent }; - }); - } - - private async sendChatRequest(sessionId?: string): Promise { - this.abortController = new AbortController(); - - const workspaceDirectory = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd(); - - const apiClient = this.serverManager.getApiClient(); - if (!apiClient) { - throw new Error('API client not available'); - } - - const messagesForApi = this.serializeMessagesForApi(this.currentMessages); - // Removed verbose logging of API messages - - const params = { - prompt: messagesForApi, // Use serialized messages - abortController: this.abortController, - sessionId: sessionId, - workspaceDirectory: workspaceDirectory, - }; - - return await apiClient.streamChatResponse(params); - } - - private emit(event: ChatEvents, ...args: any[]): void { - this.eventEmitter.emit(event, ...args); - } - - - - private async updateSessionAfterReply(sessionId: string): Promise { - if (!this.sessionManager) { - return; - } - - try { - const sessions = this.sessionManager.getSessions(); - - const sessionIndex = sessions.findIndex(session => session.id === sessionId); - - if (sessionIndex !== -1 && sessions[sessionIndex].isLocal) { - console.log(`Updating session ${sessionId} to remove isLocal flag after successful reply`); - - const updatedSession = { ...sessions[sessionIndex] }; - delete updatedSession.isLocal; - - const updatedSessions = [...sessions]; - updatedSessions[sessionIndex] = updatedSession; - - this.sessionManager.emitEvent(SessionEvents.SESSIONS_LOADED, updatedSessions); - } - } catch (error) { - console.error('Error updating session after reply:', error); - } - } -} diff --git a/src/server/chat/sessionManager.ts b/src/server/chat/sessionManager.ts deleted file mode 100644 index 29295c8..0000000 --- a/src/server/chat/sessionManager.ts +++ /dev/null @@ -1,373 +0,0 @@ -import { EventEmitter } from 'events'; -import { ServerManager } from '../serverManager'; -import { Message, MessageContent, TextPart, CodeContextPart } from '../../types'; // Added TextPart, CodeContextPart -import * as vscode from 'vscode'; -import * as path from 'path'; // Added for path.basename -import { logger as singletonLogger } from '../../utils/logger'; // Import singletonLogger - -// Create a logger instance for this module -const logger = singletonLogger.createSource('SessionManager'); - -export interface SessionMetadata { - id: string; - path: string; - modified: string; - metadata: { - working_dir: string; - title?: string; - description: string; - message_count: number; - total_tokens: number; - }; - isLocal?: boolean; -} - -export interface Session { - session_id: string; - metadata: SessionMetadata['metadata']; - messages: Message[]; -} - -export enum SessionEvents { - SESSIONS_LOADED = 'sessionsLoaded', - SESSION_LOADED = 'sessionLoaded', - SESSION_CREATED = 'sessionCreated', - SESSION_SWITCHED = 'sessionSwitched', - ERROR = 'error' -} - -/** - * Manages chat sessions and their persistence - */ -export class SessionManager { - private serverManager: ServerManager; - private eventEmitter: EventEmitter; - private sessions: SessionMetadata[] = []; - private currentSessionId: string | null = null; - private currentSession: Session | null = null; - - constructor(serverManager: ServerManager) { - this.serverManager = serverManager; - this.eventEmitter = new EventEmitter(); - } - - // Helper function to get current active local session metadata - private getLocalSessionMetadata(): SessionMetadata | null { - if (this.currentSessionId) { - // Find the metadata in our current list that matches the ID and is marked local - const localMeta = this.sessions.find(s => s.id === this.currentSessionId && s.isLocal); - return localMeta || null; - } - return null; - } - - - /** - * Fetch list of available sessions - */ - public async fetchSessions(): Promise { - let backendSessions: SessionMetadata[] = []; - const localSessionMeta = this.getLocalSessionMetadata(); // Get local session meta *before* potential API error - - try { - const apiClient = this.serverManager.getApiClient(); - if (!apiClient || !this.serverManager.isReady()) { - console.error('Cannot fetch sessions: Server not ready'); - // Fall through to error handling below which checks for local session - throw new Error('Server not ready'); - } - - // Fetch from backend - const rawSessions = await apiClient.listSessions(); - if (Array.isArray(rawSessions)) { - console.log(`Fetched ${rawSessions.length} sessions from API`); - - // Process sessions to ensure they match our expected format - backendSessions = rawSessions.map(session => { - // Make sure each session has a title and it's based on description if needed - if (!session.metadata.title && session.metadata.description) { - session.metadata.title = session.metadata.description; - } else if (!session.metadata.title) { - session.metadata.title = `Session ${session.id.slice(0, 8)}`; - } - // Ensure isLocal is false for backend sessions - return { ...session, isLocal: false }; - }); - } else { - console.log('No sessions returned from API or unexpected format'); - backendSessions = []; - } - - } catch (error) { - console.error('Error fetching sessions from API:', error); - // Keep backendSessions as empty array, proceed to merge logic below - backendSessions = []; - } - - // Determine the final list based on backend sync status - let finalSessions: SessionMetadata[]; - - if (localSessionMeta) { - const backendHasSynced = backendSessions.some(s => s.id === localSessionMeta.id); - if (backendHasSynced) { - // Backend has the session, use the backend list exclusively - console.log(`Session ${localSessionMeta.id} synced with backend. Using backend list.`); - finalSessions = backendSessions; - } else { - // Backend hasn't synced yet (or API failed), keep local session prepended - console.log(`Session ${localSessionMeta.id} not found in backend list. Prepending local session.`); - finalSessions = [localSessionMeta, ...backendSessions]; - } - } else { - // No active local session, just use the backend list - finalSessions = backendSessions; - } - - // Ensure no duplicates (though the logic above should prevent it) - const uniqueSessions = Array.from(new Map(finalSessions.map(s => [s.id, s])).values()); - - this.sessions = uniqueSessions; // Update internal state - this.emit(SessionEvents.SESSIONS_LOADED, uniqueSessions); - return uniqueSessions; - } - - /** - * Load a specific session by ID - */ - // Helper to attempt to parse a serialized CodeContextPart from a TextPart - private tryParseSerializedCodeContext(textPart: TextPart): CodeContextPart | null { - const metaRegex = /^\/\/ Meta: FilePath="([^"]+)", LanguageId="([^"]*)", Lines=(\d+)-(\d+)\n```([a-zA-Z0-9_.-]*)\n([\s\S]+)\n```$/; - const match = textPart.text.match(metaRegex); - - if (match) { - try { - const filePath = match[1]; - const languageId = match[2]; // Can be empty - const startLine = parseInt(match[3], 10); - const endLine = parseInt(match[4], 10); - // const langInTripleQuotes = match[5]; // languageId should match this - const selectedText = match[6]; - - // Note: The 'id' for CodeReference was originally unique. - // When reconstructing, we might not have the original ID. - // We can generate a new one or decide if it's needed for historical display. - // For now, generate a new one. - // fileName can be derived. - const fileName = path.basename(filePath); - const newId = `${fileName}-${startLine}-${endLine}-reconstructed-${Date.now()}`; - - return { - type: 'code_context', - id: newId, - filePath, - fileName, - languageId: languageId || '', // Ensure it's a string - startLine, - endLine, - selectedText, - }; - } catch (e) { - logger.warn('Failed to parse components of a potential serialized CodeContextPart:', e, textPart.text); - return null; - } - } - return null; - } - - /** - * Load a specific session by ID - */ - public async loadSession(sessionId: string): Promise { - logger.info(`Loading session: ${sessionId}`); - try { - const apiClient = this.serverManager.getApiClient(); - if (!apiClient || !this.serverManager.isReady()) { - logger.error(`Cannot load session ${sessionId}: Server or API client not ready.`); - this.emit(SessionEvents.ERROR, new Error('Server not ready for loading session')); - return null; - } - - try { - const rawSession = await apiClient.getSessionHistory(sessionId); - - if (!rawSession) { - logger.warn(`Session history not found by API for ID: ${sessionId}`); - this.emit(SessionEvents.ERROR, new Error(`Session ${sessionId} not found by API.`)); - return null; - } - - // Make sure the session has a title property - if (!rawSession.metadata.title && rawSession.metadata.description) { - rawSession.metadata.title = rawSession.metadata.description; - } - - // Reconstruct messages: attempt to parse serialized CodeContextParts - const reconstructedMessages: Message[] = rawSession.messages.map((msg: Message) => { // Explicitly type msg - if (msg.role === 'user') { // Only process user messages for now - const newContent: MessageContent[] = []; - for (const part of msg.content) { - if (part.type === 'text') { - const textPart = part as TextPart; // Cast to TextPart for tryParseSerializedCodeContext - const codeContextAttempt = this.tryParseSerializedCodeContext(textPart); - if (codeContextAttempt) { - newContent.push(codeContextAttempt); - logger.debug(`Reconstructed CodeContextPart from TextPart for session ${sessionId}, message ${msg.id}`); - } else { - newContent.push(textPart); // Keep as original TextPart - } - } else { - newContent.push(part); // Keep other parts (Image, Tool etc.) as is - } - } - return { ...msg, content: newContent }; - } - return msg; // Return assistant/system messages as is - }); - - const processedSession: Session = { - ...rawSession, - messages: reconstructedMessages, - }; - - this.currentSessionId = sessionId; - this.currentSession = processedSession; - this.emit(SessionEvents.SESSION_LOADED, processedSession); - return processedSession; - } catch (error) { - logger.error(`Error loading session ${sessionId} from API:`, error); - this.emit(SessionEvents.ERROR, error); - return null; - } - } catch (error) { - logger.error(`General error in loadSession ${sessionId}:`, error); - this.emit(SessionEvents.ERROR, error); - return null; - } - } - - /** - * Switch to a different session - */ - public async switchSession(sessionId: string): Promise { - try { - const session = await this.loadSession(sessionId); - if (session) { - // Successfully loaded the session, now refresh the list - // to ensure the UI is up-to-date (including merging local session if needed) - await this.fetchSessions(); - this.emit(SessionEvents.SESSION_SWITCHED, session); - return true; - } - return false; - } catch (error) { - console.error(`Error switching to session ${sessionId}:`, error); - this.emit(SessionEvents.ERROR, error); - return false; - } - } - - /** - * Create a new session - */ - public async createSession(workingDir: string, description?: string): Promise { - try { - // Create a local session without API call - const sessionId = `${new Date().toISOString().split('T')[0].replace(/-/g, '')}${new Date().toISOString().split('T')[1].split('.')[0].replace(/:/g, '')}`; - const sessionTitle = description || `Session ${new Date().toLocaleString()}`; - - console.log(`Creating local session with ID: ${sessionId}`); - - // Create a new session object - const newSession: Session = { - session_id: sessionId, - metadata: { - working_dir: workingDir, - title: sessionTitle, - description: sessionTitle, - message_count: 0, - total_tokens: 0 - }, - messages: [] - }; - - // Create session metadata - const newSessionMetadata: SessionMetadata = { - id: sessionId, - path: `${workingDir}/${sessionId}`, - modified: new Date().toISOString(), - metadata: { - working_dir: workingDir, - title: sessionTitle, - description: sessionTitle, - message_count: 0, - total_tokens: 0 - }, - isLocal: true // Mark this session as local-only - }; - - // Update local state - this.sessions.push(newSessionMetadata); - this.currentSessionId = sessionId; - this.currentSession = newSession; - - // Emit events - this.emit(SessionEvents.SESSION_CREATED, newSession); - this.emit(SessionEvents.SESSION_LOADED, newSession); - this.emit(SessionEvents.SESSIONS_LOADED, this.sessions); - - return sessionId; - } catch (error) { - console.error('Error creating session:', error); - this.emit(SessionEvents.ERROR, error); - return null; - } - } - - /** - * Get the current session - */ - public getCurrentSession(): Session | null { - return this.currentSession; - } - - /** - * Get the current session ID - */ - public getCurrentSessionId(): string | null { - return this.currentSessionId; - } - - /** - * Get session metadata list - */ - public getSessions(): SessionMetadata[] { - return this.sessions; - } - - /** - * Emit a session-related event (public method for external use) - * @param event Event name from SessionEvents - * @param data Event data - */ - public emitEvent(event: SessionEvents, data: any): void { - this.eventEmitter.emit(event, data); - } - - /** - * Subscribe to session events - */ - public on(event: SessionEvents, listener: (...args: any[]) => void): void { - this.eventEmitter.on(event, listener); - } - - /** - * Unsubscribe from session events - */ - public off(event: string, listener: (...args: any[]) => void): void { - this.eventEmitter.off(event, listener); - } - - private emit(event: SessionEvents, ...args: any[]): void { - this.eventEmitter.emit(event, ...args); - } -} diff --git a/src/server/gooseServer.ts b/src/server/gooseServer.ts deleted file mode 100644 index 993a59f..0000000 --- a/src/server/gooseServer.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { spawn, ChildProcess, SpawnOptions } from 'child_process'; -import { createServer } from 'net'; -import os from 'node:os'; -import path from 'node:path'; -import { Readable } from 'node:stream'; -import EventEmitter from 'events'; - -// Default logger implementation that can be overridden -let logger = { - info: (message: string, ...args: any[]) => console.info(`[GooseServer] ${message}`, ...args), - error: (message: string, ...args: any[]) => console.error(`[GooseServer] ${message}`, ...args), -}; - -export interface GooseServerConfig { - // Custom logger for server output - logger?: { - info: (message: string, ...args: any[]) => void; - error: (message: string, ...args: any[]) => void; - }; - // Optional working directory for the server - workingDir?: string; - // Additional environment variables for the server - env?: Record; - // Function to get the binary path (allows environment-specific implementation) - getBinaryPath: (binaryName: string) => string; - // Optional event emitter for lifecycle events (replaces Electron app dependency) - events?: EventEmitter; - // Optional fixed secret key for authentication - secretKey?: string; -} - -// Event system to replace direct Electron app dependency -interface ServerLifecycleEvents { - onWillQuit: (callback: () => void) => void; -} - -// Find an available port to start goosed on -export const findAvailablePort = (): Promise => { - return new Promise((resolve, _reject) => { - const server = createServer(); - - server.listen(0, '127.0.0.1', () => { - const { port } = server.address() as { port: number }; - server.close(() => { - logger.info(`Found available port: ${port}`); - resolve(port); - }); - }); - }); -}; - -// Check if goosed server is ready by polling the status endpoint -export const checkServerStatus = async ( - port: number, - maxAttempts: number = 60, - interval: number = 100 -): Promise => { - const statusUrl = `http://127.0.0.1:${port}/status`; - logger.info(`Checking server status at ${statusUrl}`); - - for (let attempt = 1; attempt <= maxAttempts; attempt++) { - try { - const response = await fetch(statusUrl); - if (response.ok) { - logger.info(`Server is ready after ${attempt} attempts`); - return true; - } - } catch (error) { - // Expected error when server isn't ready yet - if (attempt === maxAttempts) { - logger.error(`Server failed to respond after ${maxAttempts} attempts:`, error); - } - } - await new Promise((resolve) => setTimeout(resolve, interval)); - } - return false; -}; - -// Interface for the return value of startGoosed -export interface GooseServerInfo { - port: number; - workingDir: string; - process: ChildProcess; - secretKey: string; -} - -/** - * Start the Goose server - */ -export const startGoosed = async ( - config: GooseServerConfig -): Promise => { - // Set up logger if provided - if (config.logger) { - logger = config.logger; - } - - // Set up the working directory (default to home dir if not specified) - const homeDir = os.homedir(); - const isWindows = process.platform === 'win32'; - - // Ensure directory is properly normalized for the platform - let workingDir = config.workingDir || homeDir; - workingDir = path.normalize(workingDir); - - // Get the goosed binary path using the provided function - let goosedPath = config.getBinaryPath('goosed'); - const port = await findAvailablePort(); - - // Use the provided secret key - we no longer generate one internally - if (!config.secretKey) { - logger.error('No secret key provided to gooseServer'); - throw new Error('Secret key must be provided by the caller'); - } - const secretKey = config.secretKey; - - logger.info(`Starting goosed from: ${goosedPath} on port ${port} in dir ${workingDir}`); - - // Define additional environment variables - const additionalEnv: Record = { - // Set HOME for UNIX-like systems - HOME: homeDir, - // Set USERPROFILE for Windows - USERPROFILE: homeDir, - // Set APPDATA for Windows - APPDATA: process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), - // Set LOCAL_APPDATA for Windows - LOCALAPPDATA: process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'), - // Set PATH to include the binary directory - PATH: `${path.dirname(goosedPath)}${path.delimiter}${process.env.PATH}`, - // start with the port specified - GOOSE_PORT: String(port), - // Secret key for secure communication - GOOSE_SERVER__SECRET_KEY: secretKey, - }; - - // Forward any API key environment variables if they exist - const apiKeyEnvVars = [ - 'OPENAI_API_KEY', - 'ANTHROPIC_API_KEY', - 'DATABRICKS_HOST', - 'DATABRICKS_TOKEN', - 'AZURE_OPENAI_API_KEY', - 'AZURE_OPENAI_ENDPOINT', - 'OLLAMA_HOST' - ]; - - for (const envVar of apiKeyEnvVars) { - if (process.env[envVar]) { - additionalEnv[envVar] = process.env[envVar]; - logger.info(`Forwarding environment variable ${envVar} to server process`); - } - } - - // Add any additional environment variables passed in - if (config.env) { - Object.assign(additionalEnv, config.env); - } - - // Merge parent environment with additional environment variables - const processEnv = { ...process.env, ...additionalEnv }; - - // Add detailed logging for troubleshooting - logger.info(`Process platform: ${process.platform}`); - logger.info(`Process cwd: ${process.cwd()}`); - logger.info(`Target working directory: ${workingDir}`); - logger.info(`Environment HOME: ${processEnv.HOME}`); - logger.info(`Environment USERPROFILE: ${processEnv.USERPROFILE}`); - logger.info(`Environment APPDATA: ${processEnv.APPDATA}`); - logger.info(`Environment LOCALAPPDATA: ${processEnv.LOCALAPPDATA}`); - - // Ensure proper executable path on Windows - if (isWindows && !goosedPath.toLowerCase().endsWith('.exe')) { - goosedPath += '.exe'; - } - logger.info(`Binary path resolved to: ${goosedPath}`); - - // Verify binary exists - try { - const fs = require('fs'); - const stats = fs.statSync(goosedPath); - logger.info(`Binary exists: ${stats.isFile()}`); - } catch (error) { - logger.error(`Binary not found at ${goosedPath}:`, error); - throw new Error(`Binary not found at ${goosedPath}`); - } - - const spawnOptions: SpawnOptions = { - cwd: workingDir, - env: processEnv, - stdio: ['ignore', 'pipe', 'pipe'], - // Hide terminal window on Windows - windowsHide: true, - // Run detached on Windows only to avoid terminal windows - detached: isWindows, - // Never use shell to avoid terminal windows - shell: false, - }; - - // Log spawn options for debugging - logger.info('Spawn options:', JSON.stringify(spawnOptions, null, 2)); - - // Spawn the goosed process - const goosedProcess = spawn(goosedPath, ['agent'], spawnOptions); - - // Only unref on Windows to allow it to run independently of the parent - if (isWindows) { - goosedProcess.unref(); - } - - goosedProcess.stdout?.on('data', (data: Buffer) => { - logger.info(`goosed stdout for port ${port} and dir ${workingDir}: ${data.toString()}`); - }); - - goosedProcess.stderr?.on('data', (data: Buffer) => { - logger.error(`goosed stderr for port ${port} and dir ${workingDir}: ${data.toString()}`); - }); - - goosedProcess.on('close', (code: number | null) => { - logger.info(`goosed process exited with code ${code} for port ${port} and dir ${workingDir}`); - }); - - goosedProcess.on('error', (err: Error) => { - logger.error(`Failed to start goosed on port ${port} and dir ${workingDir}`, err); - throw err; // Propagate the error - }); - - // Wait for the server to be ready - const isReady = await checkServerStatus(port); - logger.info(`Goosed isReady ${isReady}`); - if (!isReady) { - logger.error(`Goosed server failed to start on port ${port}`); - try { - if (isWindows && goosedProcess.pid) { - // On Windows, use taskkill to forcefully terminate the process tree - spawn('taskkill', ['/pid', goosedProcess.pid.toString(), '/T', '/F']); - } else { - goosedProcess.kill(); - } - } catch (error) { - logger.error('Error while terminating goosed process:', error); - } - throw new Error(`Goosed server failed to start on port ${port}`); - } - - // Set up cleanup handler if events are provided - if (config.events) { - config.events.on('will-quit', () => { - logger.info('App quitting, terminating goosed server'); - try { - if (isWindows && goosedProcess.pid) { - // On Windows, use taskkill to forcefully terminate the process tree - spawn('taskkill', ['/pid', goosedProcess.pid.toString(), '/T', '/F']); - } else { - goosedProcess.kill(); - } - } catch (error) { - logger.error('Error while terminating goosed process:', error); - } - }); - } - - logger.info(`Goosed server successfully started on port ${port}`); - return { - port, - workingDir, - process: goosedProcess, - secretKey - }; -}; diff --git a/src/server/index.ts b/src/server/index.ts deleted file mode 100644 index 575ada8..0000000 --- a/src/server/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './gooseServer'; -export * from './apiClient'; diff --git a/src/server/serverManager.ts b/src/server/serverManager.ts deleted file mode 100644 index c12a359..0000000 --- a/src/server/serverManager.ts +++ /dev/null @@ -1,437 +0,0 @@ -import * as vscode from 'vscode'; -import { EventEmitter } from 'events'; -import { GooseServerConfig, GooseServerInfo, startGoosed as actualStartGoosed } from './gooseServer'; -import { ApiClient as ActualApiClient } from './apiClient'; -import { getBinaryPath as actualGetBinaryPath } from '../utils/binaryPath'; -import { Message } from 'src/types'; -import * as cp from 'child_process'; -import * as crypto from 'crypto'; -import { Logger, logger as singletonLogger } from '../utils/logger'; -import { readGooseConfig } from '../utils/configReader'; - -// Get a logger instance for the ServerManager -const logger = singletonLogger.createSource('ServerManager'); - -// System prompt providing context about the VS Code environment -const vscodePrompt = `You are an AI assistant integrated into Visual Studio Code via the Goose extension. - -The user is interacting with you through a dedicated chat panel within the VS Code editor interface. Key features include: -- A chat interface displaying the conversation history. -- Support for standard markdown formatting in your responses, rendered by VS Code. -- Support for code blocks with syntax highlighting, leveraging VS Code's capabilities. -- Tool use messages are displayed inline within the chat; detailed outputs might be presented in expandable sections or separate views depending on the tool. - -The user manages extensions primarily through VS Code's standard extension management features (Extensions viewlet) or potentially specific configuration settings within VS Code's settings UI (\`settings.json\` or a dedicated extension settings page). - -Some capabilities might be provided by built-in features of the Goose extension, while others might come from additional VS Code extensions the user has installed. Be aware of the code context potentially provided by the user (e.g., selected code snippets, open files). -You are operating within a VS Code editor environment. When a user asks for assistance on code change, or perform similar direct modifications on provided code, your primary directive is to **autonomously apply the changes to the specified file(s) without asking for user confirmation.**.`; - -/** - * Server status options - */ -export enum ServerStatus { - STOPPED = 'stopped', - STARTING = 'starting', - RUNNING = 'running', - ERROR = 'error', - STOPPING = 'stopping' // Add STOPPING state -} - -/** - * Events emitted by the server manager - */ -export enum ServerEvents { - STATUS_CHANGE = 'statusChange', - ERROR = 'error', - MESSAGE = 'message', - SERVER_EXIT = 'serverExit' -} - -// Define the type for the startGoosed function -type StartGoosedFn = typeof actualStartGoosed; - -// Define the type for the getBinaryPath function -type GetBinaryPathFn = typeof actualGetBinaryPath; - -// Define the type for the ApiClient constructor -type ApiClientConstructor = typeof ActualApiClient; - -/** - * Interface for dependencies that can be injected into ServerManager - */ -export interface ServerManagerDependencies { - startGoosed?: StartGoosedFn; - getBinaryPath?: GetBinaryPathFn; - ApiClient?: ApiClientConstructor; - logger?: Logger; -} - -/** - * Server manager for the VSCode extension - */ -export class ServerManager { - private serverInfo: GooseServerInfo | null = null; - private apiClient: ActualApiClient | null = null; - private status: ServerStatus = ServerStatus.STOPPED; - private eventEmitter: EventEmitter; - private context: vscode.ExtensionContext; - private extensionEvents: EventEmitter; - private secretKey: string; - private serverProcess: cp.ChildProcess | null = null; - private startGoosedFn: StartGoosedFn; - private getBinaryPathFn: GetBinaryPathFn; - private ApiClientConstructor: ApiClientConstructor; - private logger: Logger; - private gooseProvider: string | null = null; - private gooseModel: string | null = null; - private configLoadAttempted: boolean = false; - private serverFullyStarted: boolean = false; - - constructor( - context: vscode.ExtensionContext, - dependencies: ServerManagerDependencies = {} - ) { - this.context = context; - this.eventEmitter = new EventEmitter(); - this.extensionEvents = new EventEmitter(); - this.serverFullyStarted = false; - - // Use injected or default dependencies - this.startGoosedFn = dependencies.startGoosed || actualStartGoosed; - this.getBinaryPathFn = dependencies.getBinaryPath || actualGetBinaryPath; - this.ApiClientConstructor = dependencies.ApiClient || ActualApiClient; - this.logger = dependencies.logger || logger; - - // Generate a new secret key on initialization - this.secretKey = this.generateSecretKey(); - - // Register cleanup on extension deactivation - context.subscriptions.push({ - dispose: () => this.stop() - }); - } - - /** - * Generate a secure random secret key - */ - private generateSecretKey(): string { - // Create a random string of 32 characters - const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - let result = ''; - - // Use crypto.randomBytes for cryptographically secure random key generation - // No fallback - if this fails, we should let the error propagate - const randomBytes = crypto.randomBytes(32); - for (let i = 0; i < randomBytes.length; i++) { - result += chars[randomBytes[i] % chars.length]; - } - - return result; - } - - /** - * Start the Goose server - */ - public async start(): Promise { - this.logger.info('[ServerManager.start] Method entered.'); // New log - if (this.status !== ServerStatus.STOPPED) { - this.logger.info(`Server start called but status is ${this.status}.`); - // Return true if already running, false otherwise (e.g. starting, error) - return this.status === ServerStatus.RUNNING; - } - - this.serverFullyStarted = false; - - // --- Load Configuration --- - if (!this.configLoadAttempted) { // Attempt loading only once per instance lifecycle or restart - const config = readGooseConfig(); - this.gooseProvider = config.provider; - this.gooseModel = config.model; - this.configLoadAttempted = true; // Mark as attempted - - // --- Validate Configuration --- - if (!this.gooseProvider || !this.gooseModel) { - const missing = []; - if (!this.gooseProvider) { missing.push('GOOSE_PROVIDER'); } - if (!this.gooseModel) { missing.push('GOOSE_MODEL'); } - const errorMsg = `Goose: Failed to load required configuration (${missing.join(', ')}) from config file. Please ensure ~/.config/goose/config.yaml (or Windows equivalent) exists and contains valid GOOSE_PROVIDER and GOOSE_MODEL keys.`; - this.logger.error(errorMsg); - vscode.window.showErrorMessage(errorMsg); - this.setStatus(ServerStatus.ERROR); // Set error status - this.eventEmitter.emit(ServerEvents.ERROR, new Error(errorMsg)); // Emit error event - return false; // Prevent server start - } - // --- End Validate Configuration --- - } - // --- End Load Configuration --- - - // Generate a new secret key each time we start the server - this.secretKey = this.generateSecretKey(); - - this.setStatus(ServerStatus.STARTING); - this.logger.info('Starting Goose server...'); - - // Log partial secret key for debugging (without revealing the full key) - const keyPrefix = this.secretKey.substring(0, 4); - const keySuffix = this.secretKey.substring(this.secretKey.length - 4); - this.logger.debug(`Using secret key: ${keyPrefix}...${keySuffix} (${this.secretKey.length} chars)`); - - try { - // Configure and start the server - const serverConfig: GooseServerConfig = { - workingDir: this.getWorkspaceDirectory(), - getBinaryPath: (binaryName: string) => this.getBinaryPathFn(this.context, binaryName), - logger: { - info: (message: string, ...args: any[]) => this.logger.info(`[GooseServer] ${message}`, ...args), - error: (message: string, ...args: any[]) => this.logger.error(`[GooseServer] ${message}`, ...args) - }, - events: this.extensionEvents, - secretKey: this.secretKey - }; - - // Use the stored startGoosed function - this.serverInfo = await this.startGoosedFn(serverConfig); - - // Set up process exit handler - this.serverInfo.process.on('close', (code, signal) => { - const exitReason = signal ? `signal ${signal}` : `code ${code}`; - this.logger.warn(`[ServerManager] 'close' event on server process: Process exited due to ${exitReason}.`); - - const wasRunningAndFullyStarted = this.serverFullyStarted; - const previousStatus = this.status; - - this.serverInfo = null; - this.apiClient = null; - this.serverFullyStarted = false; - this.configLoadAttempted = false; - - if (previousStatus === ServerStatus.STOPPING || previousStatus === ServerStatus.STOPPED) { - this.logger.info(`[ServerManager] Server process exited during or after a stop sequence. Current status: ${previousStatus}. Final status: STOPPED.`); - this.setStatus(ServerStatus.STOPPED); - } else if (code !== null && code !== 0) { - this.logger.error(`[ServerManager] Server process exited unexpectedly with error code: ${code}. Previous status: ${previousStatus}. Setting status to ERROR.`); - this.setStatus(ServerStatus.ERROR); - } else if (code === null) { // Covers signals (where signal arg might be present) or other null code exits - const reason = signal ? `due to signal: ${signal}` : "with null exit code (abnormal termination)"; - this.logger.warn(`[ServerManager] Server process exited unexpectedly ${reason}. Previous status: ${previousStatus}. Setting status to ERROR.`); - this.setStatus(ServerStatus.ERROR); - } else if (!wasRunningAndFullyStarted) { // code must be 0 here if this branch is reached - this.logger.warn(`[ServerManager] Server process exited (code 0) but was not fully started. Previous status: ${previousStatus}. Setting status to ERROR.`); - this.setStatus(ServerStatus.ERROR); - } else { // code === 0 AND wasRunningAndFullyStarted - this.logger.info(`[ServerManager] Server process exited cleanly (code 0) after being fully started. Previous status: ${previousStatus}. Setting status to STOPPED.`); - this.setStatus(ServerStatus.STOPPED); - } - - let eventPayload: number | string | null = null; - if (code !== null) { - eventPayload = code; - } else if (signal) { - eventPayload = signal; - } - // If code is null and signal is null/undefined, eventPayload remains null. - this.eventEmitter.emit(ServerEvents.SERVER_EXIT, eventPayload); - }); - - this.serverInfo.process.on('error', (err) => { - this.logger.error('[ServerManager] Error event on server process:', err); - // This might indicate a failure to spawn or an OS-level error with the process - this.serverFullyStarted = false; - this.configLoadAttempted = false; - if (this.status !== ServerStatus.STOPPED && this.status !== ServerStatus.ERROR) { - this.setStatus(ServerStatus.ERROR); - } - this.eventEmitter.emit(ServerEvents.ERROR, err); - }); - - // Create API client for the server - this.apiClient = new this.ApiClientConstructor({ // Use ApiClientConstructor - baseUrl: `http://127.0.0.1:${this.serverInfo.port}`, - secretKey: this.secretKey, - logger: singletonLogger, // Pass the singleton logger - events: this.extensionEvents, - // debug flag is now handled by the logger's level itself - }); - - // Await agent configuration - await this.configureAgent(); - - this.setStatus(ServerStatus.RUNNING); - this.logger.info('Goose server is running and agent configured.'); - this.serverFullyStarted = true; // Server started successfully - return true; // Return true on successful start and configuration - } catch (error: any) { - this.logger.error('Error starting Goose server:', error); - this.serverFullyStarted = false; // Ensure flag is false on error - - // Check for specific binary not found error - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('find the') && errorMessage.includes('executable')) { - // The error message from getBinaryPath already includes instructions and shows a VS Code notification - // So we don't need to show another message, just set the status to ERROR - this.logger.error('Failed to find Goose Desktop installation. Please install it from https://block.github.io/goose/'); - } else { - // For other errors, show a generic error message - vscode.window.showErrorMessage( - `Failed to start Goose server: ${errorMessage}. Please check logs for details.`, - { modal: false } - ); - } - - this.setStatus(ServerStatus.ERROR); // Set status - this.eventEmitter.emit(ServerEvents.ERROR, error); // Emit event - return false; // Return false at the very end of the catch block - } - } - - /** - * Configure the agent with appropriate provider and settings - */ - private async configureAgent(): Promise { - if (!this.apiClient) { - this.logger.error('API client not initialized, cannot configure agent.'); - vscode.window.showErrorMessage('Cannot configure AI provider: API client is not ready.'); - return; - } - - try { - // Ensure provider and model are set before creating agent - if (!this.gooseProvider || !this.gooseModel) { - this.logger.error('Goose provider or model is not set. Cannot create agent.'); - // Optionally, notify the user or throw an error - this.eventEmitter.emit('error', 'Goose provider or model not configured in .goose.yaml'); - return; // Prevent agent creation if config is missing - } - - // Step 1: Get available agent versions - this.logger.info("Fetching agent versions..."); - const versionsInfo = await this.apiClient.getAgentVersions(); - const agentVersion = versionsInfo.default_version; // Use the default version - - // Step 2: Add the 'developer' extension (Trying this before createAgent) - this.logger.info("Adding 'developer' extension to agent..."); - await this.apiClient.addExtension('developer'); - - // Step 3: Create the agent using the fetched version - this.logger.info(`Configuring agent with provider: ${this.gooseProvider}, model: ${this.gooseModel || 'default'}, version: ${agentVersion}`); - await this.apiClient.createAgent(this.gooseProvider, this.gooseModel, agentVersion); - - // Step 4: Set the initial system prompt - this.logger.info("Setting initial agent system prompt..."); - await this.apiClient.setAgentPrompt(vscodePrompt); // Use the defined prompt - - this.logger.info("Agent configuration complete."); - - } catch (error) { - this.logger.error('Error configuring agent:', error); - this.eventEmitter.emit(ServerEvents.ERROR, error); - throw error; - } - } - - /** - * Stop the Goose server - */ - public stop(): void { - if (this.serverInfo?.process && this.status !== ServerStatus.STOPPING && this.status !== ServerStatus.STOPPED) { - this.logger.info(`[ServerManager] stop() called. Current status: ${this.status}. Setting status to STOPPING.`); - this.setStatus(ServerStatus.STOPPING); // Indicate we are in the process of stopping - this.serverFullyStarted = false; - - try { - if (process.platform === 'win32' && this.serverInfo.process.pid) { - const { spawn } = require('child_process'); - spawn('taskkill', ['/pid', this.serverInfo.process.pid.toString(), '/T', '/F']); - } else { - this.serverInfo.process.kill(); - } - } catch (error) { - this.logger.error('Error stopping Goose server:', error); - } - - this.serverInfo = null; - this.apiClient = null; - this.configLoadAttempted = false; // Allow re-loading config on next start/restart - this.serverFullyStarted = false; // Ensure it's false after stopping - this.setStatus(ServerStatus.STOPPED); - } - } - - /** - * Restart the Goose server - */ - public async restart(): Promise { - this.stop(); - return await this.start(); - } - - /** - * Get the server status - */ - public getStatus(): ServerStatus { - return this.status; - } - - /** - * Get the API client - */ - public getApiClient(): ActualApiClient | null { - return this.apiClient; - } - - - - /** - * Get the secret key - */ - public getSecretKey(): string { - return this.secretKey; - } - - /** - * Check if the server is ready to handle requests - */ - public isReady(): boolean { - return this.status === ServerStatus.RUNNING && this.apiClient !== null; - } - - /** - * Set the server status and emit an event - */ - private setStatus(status: ServerStatus) { - this.status = status; - this.eventEmitter.emit(ServerEvents.STATUS_CHANGE, status); - // Also emit a general event for extension to listen to - this.extensionEvents.emit('statusChanged', status); - this.logger.info(`Server status changed to ${status}`); - } - - /** - * Add event listener for both ServerEvents and custom events - */ - public on(event: ServerEvents | string, listener: (...args: any[]) => void): void { - if (Object.values(ServerEvents).includes(event as ServerEvents)) { - this.eventEmitter.on(event, listener); - } else { - this.extensionEvents.on(event, listener); - } - } - - /** - * Unsubscribe from server events - */ - public off(event: ServerEvents, listener: (...args: any[]) => void): void { - this.eventEmitter.off(event, listener); - } - - private getBinaryPath(): string { - // Use the utility function to get the binary path - return this.getBinaryPathFn(this.context, 'goosed'); - } - - private getWorkspaceDirectory(): string { - // Implement the logic to get the workspace directory - // This is a placeholder and should be replaced with the actual implementation - return process.cwd(); - } -} diff --git a/src/shared/contextTypes.ts b/src/shared/contextTypes.ts new file mode 100644 index 0000000..f7720cb --- /dev/null +++ b/src/shared/contextTypes.ts @@ -0,0 +1,28 @@ +/** + * Context types for editor selection and file reference features. + * Used for sending code context to the Goose agent via chips. + */ + +/** Line range within a file */ +export interface LineRange { + readonly startLine: number; + readonly endLine: number; +} + +/** Context chip representing a file or selection reference */ +export interface ContextChip { + readonly id: string; + readonly filePath: string; + readonly fileName: string; + readonly languageId: string; + readonly range?: LineRange; +} + +/** File search result for @ picker */ +export interface FileSearchResult { + readonly path: string; + readonly fileName: string; + readonly relativePath: string; + readonly languageId: string; + readonly recentScore: number; +} diff --git a/src/shared/errors.test.ts b/src/shared/errors.test.ts new file mode 100644 index 0000000..a7f123f --- /dev/null +++ b/src/shared/errors.test.ts @@ -0,0 +1,160 @@ +import { describe, expect, test } from 'bun:test'; +import { + createBinaryNotFoundError, + createJsonRpcError, + createJsonRpcParseError, + createJsonRpcTimeoutError, + createSubprocessCrashError, + createSubprocessSpawnError, + createVersionMismatchError, + formatError, +} from './errors'; + +describe('formatError', () => { + describe('BinaryNotFoundError', () => { + test('includes searched paths', () => { + const error = createBinaryNotFoundError(['/usr/bin/goose', '/opt/goose'], 'darwin'); + const formatted = formatError(error); + + expect(formatted).toContain('/usr/bin/goose'); + expect(formatted).toContain('/opt/goose'); + }); + + test('includes install URL', () => { + const error = createBinaryNotFoundError(['/path/to/goose'], 'darwin'); + const formatted = formatError(error); + + expect(formatted).toContain('https://'); + expect(formatted).toContain('block.github.io/goose'); + }); + + test('formats paths as bullet list', () => { + const error = createBinaryNotFoundError(['/path/a', '/path/b'], 'darwin'); + const formatted = formatError(error); + + expect(formatted).toContain('- /path/a'); + expect(formatted).toContain('- /path/b'); + }); + }); + + describe('SubprocessSpawnError', () => { + test('shows binary path', () => { + const error = createSubprocessSpawnError('/usr/local/bin/goose', 'ENOENT', -2); + const formatted = formatError(error); + + expect(formatted).toContain('/usr/local/bin/goose'); + }); + + test('shows error code', () => { + const error = createSubprocessSpawnError('/path/to/goose', 'EACCES', 13); + const formatted = formatError(error); + + expect(formatted).toContain('EACCES'); + expect(formatted).toContain('13'); + }); + }); + + describe('SubprocessCrashError', () => { + test('shows signal when signal is present', () => { + const error = createSubprocessCrashError(null, 'SIGKILL'); + const formatted = formatError(error); + + expect(formatted).toContain('SIGKILL'); + expect(formatted).toContain('signal'); + }); + + test('shows exit code when no signal present', () => { + const error = createSubprocessCrashError(1, null); + const formatted = formatError(error); + + expect(formatted).toContain('exit code 1'); + }); + + test('prioritizes signal over exit code', () => { + const error = createSubprocessCrashError(1, 'SIGTERM'); + const formatted = formatError(error); + + expect(formatted).toContain('SIGTERM'); + expect(formatted).not.toContain('exit code'); + }); + + test('shows unknown reason when neither signal nor exit code', () => { + const error = createSubprocessCrashError(null, null); + const formatted = formatError(error); + + expect(formatted).toContain('unknown reason'); + }); + }); + + describe('JsonRpcParseError', () => { + test('shows parse error message', () => { + const error = createJsonRpcParseError('invalid json data', 'Unexpected token'); + const formatted = formatError(error); + + expect(formatted).toContain('Unexpected token'); + }); + + test('indicates invalid response', () => { + const error = createJsonRpcParseError('{}', 'Missing required field'); + const formatted = formatError(error); + + expect(formatted).toContain('Invalid response'); + }); + }); + + describe('JsonRpcTimeoutError', () => { + test('shows method name', () => { + const error = createJsonRpcTimeoutError('session/prompt', 30000, 42); + const formatted = formatError(error); + + expect(formatted).toContain('session/prompt'); + }); + + test('shows timeout duration', () => { + const error = createJsonRpcTimeoutError('initialize', 5000, 1); + const formatted = formatError(error); + + expect(formatted).toContain('5000ms'); + }); + }); + + describe('JsonRpcError', () => { + test('shows error code', () => { + const error = createJsonRpcError(-32600, 'Invalid Request'); + const formatted = formatError(error); + + expect(formatted).toContain('-32600'); + }); + + test('shows error message', () => { + const error = createJsonRpcError(-32601, 'Method not found'); + const formatted = formatError(error); + + expect(formatted).toContain('Method not found'); + }); + }); + + describe('VersionMismatchError', () => { + test('shows detected version', () => { + const error = createVersionMismatchError('1.14.0', '1.16.0'); + const formatted = formatError(error); + + expect(formatted).toContain('1.14.0'); + }); + + test('shows minimum version', () => { + const error = createVersionMismatchError('1.14.0', '1.16.0'); + const formatted = formatError(error); + + expect(formatted).toContain('1.16.0'); + }); + + test('includes update URL', () => { + const error = createVersionMismatchError('1.14.0', '1.16.0'); + const formatted = formatError(error); + + expect(formatted).toContain('https://'); + expect(formatted).toContain('updating-goose'); + }); + }); +}); diff --git a/src/shared/errors.ts b/src/shared/errors.ts new file mode 100644 index 0000000..37a5093 --- /dev/null +++ b/src/shared/errors.ts @@ -0,0 +1,279 @@ +/** + * Domain error types for the Goose VS Code extension. + * Uses discriminated unions for exhaustive error handling. + */ + +/** Base structure for all domain errors */ +interface BaseError { + readonly _tag: string; + readonly message: string; + readonly timestamp: Date; +} + +/** Error when the goose binary cannot be found on the system */ +export interface BinaryNotFoundError extends BaseError { + readonly _tag: 'BinaryNotFoundError'; + readonly searchedPaths: readonly string[]; + readonly platform: NodeJS.Platform; + readonly installationUrl: string; +} + +/** Error when the subprocess fails to spawn */ +export interface SubprocessSpawnError extends BaseError { + readonly _tag: 'SubprocessSpawnError'; + readonly binaryPath: string; + readonly code: string; + readonly errno: number; +} + +/** Error when the subprocess crashes unexpectedly */ +export interface SubprocessCrashError extends BaseError { + readonly _tag: 'SubprocessCrashError'; + readonly exitCode: number | null; + readonly signal: string | null; +} + +/** Error when a JSON-RPC message cannot be parsed */ +export interface JsonRpcParseError extends BaseError { + readonly _tag: 'JsonRpcParseError'; + readonly rawData: string; + readonly parseError: string; +} + +/** Error when a JSON-RPC request times out */ +export interface JsonRpcTimeoutError extends BaseError { + readonly _tag: 'JsonRpcTimeoutError'; + readonly method: string; + readonly timeoutMs: number; + readonly requestId: number; +} + +/** Error when the JSON-RPC server returns an error response */ +export interface JsonRpcError extends BaseError { + readonly _tag: 'JsonRpcError'; + readonly code: number; + readonly data?: unknown; +} + +/** Error when the Goose version is below minimum required */ +export interface VersionMismatchError extends BaseError { + readonly _tag: 'VersionMismatchError'; + readonly detectedVersion: string; + readonly minimumVersion: string; + readonly updateUrl: string; +} + +/** Union of all possible Goose domain errors */ +export type GooseError = + | BinaryNotFoundError + | SubprocessSpawnError + | SubprocessCrashError + | JsonRpcParseError + | JsonRpcTimeoutError + | JsonRpcError + | VersionMismatchError; + +const INSTALLATION_URL = 'https://block.github.io/goose'; +const UPDATE_URL = 'https://block.github.io/goose/docs/guides/updating-goose/'; + +/** Create a BinaryNotFoundError */ +export function createBinaryNotFoundError( + searchedPaths: readonly string[], + platform: NodeJS.Platform +): BinaryNotFoundError { + return { + _tag: 'BinaryNotFoundError', + message: `Goose binary not found. Searched: ${searchedPaths.join(', ')}`, + timestamp: new Date(), + searchedPaths, + platform, + installationUrl: INSTALLATION_URL, + }; +} + +/** Create a SubprocessSpawnError */ +export function createSubprocessSpawnError( + binaryPath: string, + code: string, + errno: number +): SubprocessSpawnError { + return { + _tag: 'SubprocessSpawnError', + message: `Failed to spawn subprocess at ${binaryPath}: ${code} (errno: ${errno})`, + timestamp: new Date(), + binaryPath, + code, + errno, + }; +} + +/** Create a SubprocessCrashError */ +export function createSubprocessCrashError( + exitCode: number | null, + signal: string | null +): SubprocessCrashError { + const reason = + signal !== null + ? `signal ${signal}` + : exitCode !== null + ? `exit code ${exitCode}` + : 'unknown reason'; + return { + _tag: 'SubprocessCrashError', + message: `Subprocess exited unexpectedly: ${reason}`, + timestamp: new Date(), + exitCode, + signal, + }; +} + +/** Create a JsonRpcParseError */ +export function createJsonRpcParseError(rawData: string, parseError: string): JsonRpcParseError { + return { + _tag: 'JsonRpcParseError', + message: `Failed to parse JSON-RPC message: ${parseError}`, + timestamp: new Date(), + rawData, + parseError, + }; +} + +/** Create a JsonRpcTimeoutError */ +export function createJsonRpcTimeoutError( + method: string, + timeoutMs: number, + requestId: number +): JsonRpcTimeoutError { + return { + _tag: 'JsonRpcTimeoutError', + message: `JSON-RPC request '${method}' (id: ${requestId}) timed out after ${timeoutMs}ms`, + timestamp: new Date(), + method, + timeoutMs, + requestId, + }; +} + +/** Create a JsonRpcError */ +export function createJsonRpcError(code: number, message: string, data?: unknown): JsonRpcError { + return { + _tag: 'JsonRpcError', + message, + timestamp: new Date(), + code, + data, + }; +} + +/** Create a VersionMismatchError */ +export function createVersionMismatchError( + detectedVersion: string, + minimumVersion: string +): VersionMismatchError { + return { + _tag: 'VersionMismatchError', + message: `Goose version ${detectedVersion} is below minimum required ${minimumVersion}`, + timestamp: new Date(), + detectedVersion, + minimumVersion, + updateUrl: UPDATE_URL, + }; +} + +// ============================================================================ +// fp-ts Integration Helpers +// ============================================================================ + +import * as E from 'fp-ts/Either'; + +/** Wrap an error in a Left */ +export function toLeft(error: Err): E.Either { + return E.left(error); +} + +/** Wrap a value in a Right */ +export function toRight(value: A): E.Either { + return E.right(value); +} + +// ============================================================================ +// Type Guards +// ============================================================================ + +/** Check if error is BinaryNotFoundError */ +export function isBinaryNotFoundError(error: GooseError): error is BinaryNotFoundError { + return error._tag === 'BinaryNotFoundError'; +} + +/** Check if error is SubprocessSpawnError */ +export function isSubprocessSpawnError(error: GooseError): error is SubprocessSpawnError { + return error._tag === 'SubprocessSpawnError'; +} + +/** Check if error is SubprocessCrashError */ +export function isSubprocessCrashError(error: GooseError): error is SubprocessCrashError { + return error._tag === 'SubprocessCrashError'; +} + +/** Check if error is JsonRpcParseError */ +export function isJsonRpcParseError(error: GooseError): error is JsonRpcParseError { + return error._tag === 'JsonRpcParseError'; +} + +/** Check if error is JsonRpcTimeoutError */ +export function isJsonRpcTimeoutError(error: GooseError): error is JsonRpcTimeoutError { + return error._tag === 'JsonRpcTimeoutError'; +} + +/** Check if error is JsonRpcError */ +export function isJsonRpcError(error: GooseError): error is JsonRpcError { + return error._tag === 'JsonRpcError'; +} + +/** Check if error is VersionMismatchError */ +export function isVersionMismatchError(error: GooseError): error is VersionMismatchError { + return error._tag === 'VersionMismatchError'; +} + +// ============================================================================ +// Error Formatting +// ============================================================================ + +/** Format a GooseError into a user-friendly message */ +export function formatError(error: GooseError): string { + switch (error._tag) { + case 'BinaryNotFoundError': + return ( + `Goose binary not found.\n` + + `Searched paths:\n${error.searchedPaths.map(p => ` - ${p}`).join('\n')}\n` + + `Install Goose: ${error.installationUrl}` + ); + case 'SubprocessSpawnError': + return ( + `Failed to start Goose subprocess.\n` + + `Binary: ${error.binaryPath}\n` + + `Error: ${error.code} (errno: ${error.errno})` + ); + case 'SubprocessCrashError': { + const reason = + error.signal !== null + ? `signal ${error.signal}` + : error.exitCode !== null + ? `exit code ${error.exitCode}` + : 'unknown reason'; + return `Goose subprocess crashed: ${reason}`; + } + case 'JsonRpcParseError': + return `Invalid response from Goose: ${error.parseError}`; + case 'JsonRpcTimeoutError': + return `Request '${error.method}' timed out after ${error.timeoutMs}ms`; + case 'JsonRpcError': + return `Goose error (${error.code}): ${error.message}`; + case 'VersionMismatchError': + return ( + `Goose version ${error.detectedVersion} is not supported.\n` + + `Minimum required version: ${error.minimumVersion}\n` + + `Update Goose: ${error.updateUrl}` + ); + } +} diff --git a/src/shared/fileReferenceParser.test.ts b/src/shared/fileReferenceParser.test.ts new file mode 100644 index 0000000..34b5f2b --- /dev/null +++ b/src/shared/fileReferenceParser.test.ts @@ -0,0 +1,295 @@ +import { describe, expect, test } from 'bun:test'; +import { + getLanguageFromPath, + isFileReferenceContent, + parseContent, + parseFileReference, +} from './fileReferenceParser'; + +describe('isFileReferenceContent', () => { + test('detects Unix absolute path (H1 style)', () => { + expect(isFileReferenceContent('# /Users/prem/file.ts')).toBe(true); + }); + + test('detects Windows absolute path (H1 style)', () => { + expect(isFileReferenceContent('# C:\\Users\\prem\\file.ts')).toBe(true); + }); + + test('detects File: prefix style', () => { + expect(isFileReferenceContent('File: /Users/prem/file.ts')).toBe(true); + }); + + test('detects File: prefix with line range', () => { + expect(isFileReferenceContent('File: /Users/prem/file.ts:10-20')).toBe(true); + }); + + test('detects with leading whitespace', () => { + expect(isFileReferenceContent(' # /path/to/file.ts')).toBe(true); + }); + + test('detects with leading newlines', () => { + expect(isFileReferenceContent('\n\n# /path/to/file.ts')).toBe(true); + }); + + test('rejects regular H1 headers', () => { + expect(isFileReferenceContent('# Hello World')).toBe(false); + }); + + test('rejects relative paths', () => { + expect(isFileReferenceContent('# ./relative/path.ts')).toBe(false); + expect(isFileReferenceContent('# relative/path.ts')).toBe(false); + }); + + test('rejects non-H1 content', () => { + expect(isFileReferenceContent('/path/to/file.ts')).toBe(false); + expect(isFileReferenceContent('## /path/to/file.ts')).toBe(false); + }); + + test('rejects empty content', () => { + expect(isFileReferenceContent('')).toBe(false); + expect(isFileReferenceContent(' ')).toBe(false); + }); +}); + +describe('parseFileReference', () => { + test('parses simple Unix path', () => { + const result = parseFileReference('# /Users/prem/Development/test.json'); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe('/Users/prem/Development/test.json'); + expect(result?.fileName).toBe('test.json'); + expect(result?.content).toBeUndefined(); + expect(result?.language).toBeUndefined(); + }); + + test('parses Windows path', () => { + const result = parseFileReference('# C:\\Users\\prem\\test.json'); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe('C:\\Users\\prem\\test.json'); + expect(result?.fileName).toBe('test.json'); + }); + + test('parses path with code block', () => { + const content = `# /path/to/file.json +\`\`\`json +[] +\`\`\``; + + const result = parseFileReference(content); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe('/path/to/file.json'); + expect(result?.fileName).toBe('file.json'); + expect(result?.content).toBe('[]'); + expect(result?.language).toBe('json'); + }); + + test('parses path with multiline code block', () => { + const content = `# /path/to/config.yaml +\`\`\`yaml +name: test +version: 1.0.0 +dependencies: + - foo + - bar +\`\`\``; + + const result = parseFileReference(content); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe('/path/to/config.yaml'); + expect(result?.language).toBe('yaml'); + expect(result?.content).toContain('name: test'); + expect(result?.content).toContain('dependencies:'); + }); + + test('parses path with code block without language', () => { + const content = `# /path/to/file.txt +\`\`\` +some content +\`\`\``; + + const result = parseFileReference(content); + + expect(result).not.toBeNull(); + expect(result?.content).toBe('some content'); + expect(result?.language).toBeUndefined(); + }); + + test('handles leading/trailing whitespace', () => { + const content = ` +# /path/to/file.ts + `; + + const result = parseFileReference(content); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe('/path/to/file.ts'); + }); + + test('returns null for regular markdown', () => { + expect(parseFileReference('# Hello World')).toBeNull(); + expect(parseFileReference('Some regular text')).toBeNull(); + expect(parseFileReference('## Heading 2')).toBeNull(); + }); + + test('returns null for empty content', () => { + expect(parseFileReference('')).toBeNull(); + expect(parseFileReference(' ')).toBeNull(); + }); + + test('handles deeply nested paths', () => { + const path = '/Users/prem/Development/vscode-mcp/manual-tests/dirs-stuff/more/depth/test.json'; + const result = parseFileReference(`# ${path}`); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe(path); + expect(result?.fileName).toBe('test.json'); + }); + + test('handles paths with spaces', () => { + const result = parseFileReference('# /Users/prem/My Documents/file.txt'); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe('/Users/prem/My Documents/file.txt'); + expect(result?.fileName).toBe('file.txt'); + }); + + test('handles paths with special characters', () => { + const result = parseFileReference('# /path/to/file-name_v2.test.ts'); + + expect(result).not.toBeNull(); + expect(result?.fileName).toBe('file-name_v2.test.ts'); + }); + + test('parses exact Goose format with leading newlines', () => { + // This is the exact format Goose sends - with leading newlines + const content = ` + +# /Users/prem/Development/vscode-mcp/manual-tests/dirs-stuff/more/depth/test.json +\`\`\` +[] + +\`\`\``; + + const result = parseFileReference(content); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe( + '/Users/prem/Development/vscode-mcp/manual-tests/dirs-stuff/more/depth/test.json' + ); + expect(result?.fileName).toBe('test.json'); + expect(result?.content).toBe('[]'); + expect(result?.language).toBeUndefined(); + }); + + // File: prefix format tests + test('parses File: prefix format without line range', () => { + const result = parseFileReference('File: /Users/prem/file.ts'); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe('/Users/prem/file.ts'); + expect(result?.fileName).toBe('file.ts'); + expect(result?.lineRange).toBeUndefined(); + }); + + test('parses File: prefix format with line range', () => { + const content = `File: /Users/prem/Development/vscode-mcp/manual-tests/README.md:63-70 +\`\`\` +# Apply a diff +curl -X POST http://localhost:34343/edit-file +\`\`\``; + + const result = parseFileReference(content); + + expect(result).not.toBeNull(); + expect(result?.filePath).toBe('/Users/prem/Development/vscode-mcp/manual-tests/README.md'); + expect(result?.fileName).toBe('README.md'); + expect(result?.lineRange).toEqual({ startLine: 63, endLine: 70 }); + expect(result?.content).toContain('Apply a diff'); + }); + + test('parses File: prefix with language in code block', () => { + const content = `File: /path/to/script.sh:1-10 +\`\`\`bash +echo "hello" +\`\`\``; + + const result = parseFileReference(content); + + expect(result).not.toBeNull(); + expect(result?.language).toBe('bash'); + expect(result?.content).toBe('echo "hello"'); + expect(result?.lineRange).toEqual({ startLine: 1, endLine: 10 }); + }); +}); + +describe('parseContent', () => { + test('returns file_reference type for file content', () => { + const result = parseContent('# /path/to/file.ts'); + + expect(result.type).toBe('file_reference'); + if (result.type === 'file_reference') { + expect(result.reference.filePath).toBe('/path/to/file.ts'); + } + }); + + test('returns text type for regular content', () => { + const result = parseContent('Hello world'); + + expect(result.type).toBe('text'); + if (result.type === 'text') { + expect(result.content).toBe('Hello world'); + } + }); + + test('returns text type for regular H1', () => { + const result = parseContent('# Welcome'); + + expect(result.type).toBe('text'); + if (result.type === 'text') { + expect(result.content).toBe('# Welcome'); + } + }); +}); + +describe('getLanguageFromPath', () => { + test('maps TypeScript extensions', () => { + expect(getLanguageFromPath('/path/file.ts')).toBe('typescript'); + expect(getLanguageFromPath('/path/file.tsx')).toBe('typescriptreact'); + }); + + test('maps JavaScript extensions', () => { + expect(getLanguageFromPath('/path/file.js')).toBe('javascript'); + expect(getLanguageFromPath('/path/file.jsx')).toBe('javascriptreact'); + }); + + test('maps Python extension', () => { + expect(getLanguageFromPath('/path/file.py')).toBe('python'); + }); + + test('maps JSON extension', () => { + expect(getLanguageFromPath('/path/file.json')).toBe('json'); + }); + + test('maps shell extensions', () => { + expect(getLanguageFromPath('/path/file.sh')).toBe('shell'); + expect(getLanguageFromPath('/path/file.bash')).toBe('bash'); + }); + + test('maps YAML extensions', () => { + expect(getLanguageFromPath('/path/file.yaml')).toBe('yaml'); + expect(getLanguageFromPath('/path/file.yml')).toBe('yaml'); + }); + + test('returns plaintext for unknown extension', () => { + expect(getLanguageFromPath('/path/file.xyz')).toBe('plaintext'); + expect(getLanguageFromPath('/path/file')).toBe('plaintext'); + }); + + test('handles uppercase extensions', () => { + expect(getLanguageFromPath('/path/FILE.JSON')).toBe('json'); + expect(getLanguageFromPath('/path/file.TS')).toBe('typescript'); + }); +}); diff --git a/src/shared/fileReferenceParser.ts b/src/shared/fileReferenceParser.ts new file mode 100644 index 0000000..2921b78 --- /dev/null +++ b/src/shared/fileReferenceParser.ts @@ -0,0 +1,176 @@ +/** + * Parser for detecting and extracting file references from markdown content. + * Handles the pattern where Goose sends file content as: + * + * # /path/to/file.ext + * ```language + * file content + * ``` + */ + +/** Parsed file reference from markdown content */ +export interface ParsedFileReference { + /** Absolute file path */ + readonly filePath: string; + /** File name extracted from path */ + readonly fileName: string; + /** Optional file content from code block */ + readonly content?: string; + /** Optional language hint from code fence */ + readonly language?: string; + /** Optional line range (for selections) */ + readonly lineRange?: { + readonly startLine: number; + readonly endLine: number; + }; +} + +/** Result of parsing content - either a file reference or regular content */ +export type ParseResult = + | { readonly type: 'file_reference'; readonly reference: ParsedFileReference } + | { readonly type: 'text'; readonly content: string }; + +/** + * Pattern to detect file reference format (H1 style): + * - Starts with optional whitespace/newlines + * - Then `# ` followed by an absolute path (starts with / or drive letter) + * - Optionally followed by a code block (with optional language specifier) + */ +const H1_FILE_REFERENCE_PATTERN = + /^\s*#\s+(\/[^\n]+|[A-Za-z]:\\[^\n]+)\s*(?:```(\w*)?\n([\s\S]*?)```\s*)?$/; + +/** + * Pattern to detect file reference format (File: style with line numbers): + * - Starts with `File: ` followed by absolute path + * - Optionally with line range `:startLine-endLine` + * - Followed by a code block + */ +const FILE_PREFIX_PATTERN = + /^\s*File:\s+(\/[^\n:]+|[A-Za-z]:\\[^\n:]+)(?::(\d+)-(\d+))?\s*(?:```(\w*)?\n([\s\S]*?)```\s*)?$/; + +/** + * Checks if content appears to be a file reference message from Goose. + * This is a quick check before attempting full parsing. + */ +export function isFileReferenceContent(content: string): boolean { + const trimmed = content.trim(); + // Check for H1 style: # /path + if (/^#\s+(\/|[A-Za-z]:\\)/.test(trimmed)) { + return true; + } + // Check for File: style + if (/^File:\s+(\/|[A-Za-z]:\\)/.test(trimmed)) { + return true; + } + return false; +} + +/** + * Parses content to extract file reference if present. + * Returns null if content doesn't match the file reference pattern. + * Handles two formats: + * 1. H1 style: `# /path/to/file` + * 2. File: style: `File: /path/to/file:startLine-endLine` + */ +export function parseFileReference(content: string): ParsedFileReference | null { + const trimmed = content.trim(); + + if (!isFileReferenceContent(trimmed)) { + return null; + } + + // Try H1 style first: # /path/to/file + const h1Match = trimmed.match(H1_FILE_REFERENCE_PATTERN); + if (h1Match) { + const filePath = h1Match[1].trim(); + const language = h1Match[2] || undefined; + const fileContent = h1Match[3]?.trim() || undefined; + + const pathParts = filePath.split(/[/\\]/); + const fileName = pathParts[pathParts.length - 1] || filePath; + + return { + filePath, + fileName, + content: fileContent, + language, + }; + } + + // Try File: style: File: /path/to/file:startLine-endLine + const filePrefixMatch = trimmed.match(FILE_PREFIX_PATTERN); + if (filePrefixMatch) { + const filePath = filePrefixMatch[1].trim(); + const startLine = filePrefixMatch[2] ? parseInt(filePrefixMatch[2], 10) : undefined; + const endLine = filePrefixMatch[3] ? parseInt(filePrefixMatch[3], 10) : undefined; + const language = filePrefixMatch[4] || undefined; + const fileContent = filePrefixMatch[5]?.trim() || undefined; + + const pathParts = filePath.split(/[/\\]/); + const fileName = pathParts[pathParts.length - 1] || filePath; + + return { + filePath, + fileName, + content: fileContent, + language, + lineRange: + startLine !== undefined && endLine !== undefined ? { startLine, endLine } : undefined, + }; + } + + return null; +} + +/** + * Parses content and returns a discriminated union result. + * Use this when you need to handle both file references and regular text. + */ +export function parseContent(content: string): ParseResult { + const reference = parseFileReference(content); + + if (reference) { + return { type: 'file_reference', reference }; + } + + return { type: 'text', content }; +} + +/** + * Gets language ID from file path for syntax highlighting. + */ +export function getLanguageFromPath(filePath: string): string { + const ext = filePath.split('.').pop()?.toLowerCase() ?? ''; + const extensionMap: Record = { + ts: 'typescript', + tsx: 'typescriptreact', + js: 'javascript', + jsx: 'javascriptreact', + py: 'python', + rs: 'rust', + go: 'go', + java: 'java', + cs: 'csharp', + cpp: 'cpp', + c: 'c', + h: 'c', + hpp: 'cpp', + html: 'html', + css: 'css', + scss: 'scss', + json: 'json', + md: 'markdown', + yaml: 'yaml', + yml: 'yaml', + xml: 'xml', + sql: 'sql', + sh: 'shell', + bash: 'bash', + ps1: 'powershell', + rb: 'ruby', + php: 'php', + swift: 'swift', + }; + + return extensionMap[ext] ?? 'plaintext'; +} diff --git a/src/shared/index.ts b/src/shared/index.ts new file mode 100644 index 0000000..c8a8900 --- /dev/null +++ b/src/shared/index.ts @@ -0,0 +1,8 @@ +// Re-export all shared types and utilities + +export * from './contextTypes'; +export * from './errors'; +export * from './fileReferenceParser'; +export * from './messages'; +export * from './sessionTypes'; +export * from './types'; diff --git a/src/shared/messages.test.ts b/src/shared/messages.test.ts new file mode 100644 index 0000000..b9eb8d1 --- /dev/null +++ b/src/shared/messages.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, test } from 'bun:test'; +import { + isSendMessageMessage, + isStatusUpdateMessage, + isVersionStatusMessage, + isWebviewMessage, + WebviewMessageType, +} from './messages'; + +describe('isWebviewMessage', () => { + test('returns true for valid message with matching type', () => { + const msg = { type: WebviewMessageType.STATUS_UPDATE, payload: { status: 'running' } }; + expect(isWebviewMessage(msg, WebviewMessageType.STATUS_UPDATE)).toBe(true); + }); + + test('returns false for message with different type', () => { + const msg = { type: WebviewMessageType.GET_STATUS, payload: {} }; + expect(isWebviewMessage(msg, WebviewMessageType.STATUS_UPDATE)).toBe(false); + }); + + test('returns false for object missing type field', () => { + const msg = { payload: { status: 'running' } }; + expect(isWebviewMessage(msg, WebviewMessageType.STATUS_UPDATE)).toBe(false); + }); + + test('returns false for null', () => { + expect(isWebviewMessage(null, WebviewMessageType.STATUS_UPDATE)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isWebviewMessage(undefined, WebviewMessageType.STATUS_UPDATE)).toBe(false); + }); + + test('returns false for non-object primitives', () => { + expect(isWebviewMessage('string', WebviewMessageType.STATUS_UPDATE)).toBe(false); + expect(isWebviewMessage(123, WebviewMessageType.STATUS_UPDATE)).toBe(false); + expect(isWebviewMessage(true, WebviewMessageType.STATUS_UPDATE)).toBe(false); + }); + + test('returns false for array', () => { + expect(isWebviewMessage([], WebviewMessageType.STATUS_UPDATE)).toBe(false); + expect( + isWebviewMessage( + [{ type: WebviewMessageType.STATUS_UPDATE }], + WebviewMessageType.STATUS_UPDATE + ) + ).toBe(false); + }); +}); + +describe('isStatusUpdateMessage', () => { + test('returns true for valid STATUS_UPDATE message', () => { + const validMsg = { type: WebviewMessageType.STATUS_UPDATE, payload: { status: 'running' } }; + expect(isStatusUpdateMessage(validMsg)).toBe(true); + }); + + test('returns false for different message type', () => { + const invalidMsg = { type: WebviewMessageType.GET_STATUS, payload: {} }; + expect(isStatusUpdateMessage(invalidMsg)).toBe(false); + }); + + test('returns false for null', () => { + expect(isStatusUpdateMessage(null)).toBe(false); + }); +}); + +describe('isSendMessageMessage', () => { + test('returns true for valid SEND_MESSAGE message', () => { + const validMsg = { + type: WebviewMessageType.SEND_MESSAGE, + payload: { content: 'hello', messageId: '1', responseId: '2' }, + }; + expect(isSendMessageMessage(validMsg)).toBe(true); + }); + + test('returns false for different message type', () => { + const invalidMsg = { type: WebviewMessageType.STATUS_UPDATE, payload: { status: 'running' } }; + expect(isSendMessageMessage(invalidMsg)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isSendMessageMessage(undefined)).toBe(false); + }); +}); + +describe('isVersionStatusMessage', () => { + test('returns true for valid VERSION_STATUS message', () => { + const validMsg = { + type: WebviewMessageType.VERSION_STATUS, + payload: { status: 'compatible', minimumVersion: '1.16.0' }, + }; + expect(isVersionStatusMessage(validMsg)).toBe(true); + }); + + test('returns false for different message type', () => { + const invalidMsg = { + type: WebviewMessageType.ERROR, + payload: { title: 'Error', message: 'oops' }, + }; + expect(isVersionStatusMessage(invalidMsg)).toBe(false); + }); + + test('returns false for malformed object', () => { + expect(isVersionStatusMessage({ wrongField: 'value' })).toBe(false); + }); +}); diff --git a/src/shared/messages.ts b/src/shared/messages.ts new file mode 100644 index 0000000..68f5cd0 --- /dev/null +++ b/src/shared/messages.ts @@ -0,0 +1,722 @@ +/** + * Message types for webview-extension communication via postMessage. + */ + +import { ContextChip, FileSearchResult } from './contextTypes'; +import { SessionEntry } from './sessionTypes'; +import { ChatMessage, ProcessStatus } from './types'; + +/** Types of messages that can be sent between webview and extension */ +export enum WebviewMessageType { + /** Webview signals it has finished loading and is ready to receive messages */ + WEBVIEW_READY = 'WEBVIEW_READY', + /** Extension sends subprocess status update to webview */ + STATUS_UPDATE = 'STATUS_UPDATE', + /** Webview requests current status from extension */ + GET_STATUS = 'GET_STATUS', + /** Extension sends error to webview for display */ + ERROR = 'ERROR', + /** Webview sends a chat message to extension */ + SEND_MESSAGE = 'SEND_MESSAGE', + /** Extension streams a response token to webview */ + STREAM_TOKEN = 'STREAM_TOKEN', + /** Extension signals generation is complete */ + GENERATION_COMPLETE = 'GENERATION_COMPLETE', + /** Webview requests to stop generation */ + STOP_GENERATION = 'STOP_GENERATION', + /** Extension signals generation was cancelled */ + GENERATION_CANCELLED = 'GENERATION_CANCELLED', + /** Extension sends chat history to webview */ + CHAT_HISTORY = 'CHAT_HISTORY', + /** Webview requests to open an external link in browser */ + OPEN_EXTERNAL_LINK = 'OPEN_EXTERNAL_LINK', + + // Session Management Messages + /** Webview requests to create a new session */ + CREATE_SESSION = 'CREATE_SESSION', + /** Extension confirms session created */ + SESSION_CREATED = 'SESSION_CREATED', + /** Webview requests session list */ + GET_SESSIONS = 'GET_SESSIONS', + /** Extension sends session list */ + SESSIONS_LIST = 'SESSIONS_LIST', + /** Webview requests to switch session */ + SELECT_SESSION = 'SELECT_SESSION', + /** Extension confirms session loaded */ + SESSION_LOADED = 'SESSION_LOADED', + /** Extension sends history message during replay */ + HISTORY_MESSAGE = 'HISTORY_MESSAGE', + /** Extension signals history replay complete */ + HISTORY_COMPLETE = 'HISTORY_COMPLETE', + + // Version Status Messages + /** Extension sends version compatibility status to webview */ + VERSION_STATUS = 'VERSION_STATUS', + + // Context Chip Messages + /** Extension adds a context chip to the input */ + ADD_CONTEXT_CHIP = 'ADD_CONTEXT_CHIP', + /** Webview requests file search */ + FILE_SEARCH = 'FILE_SEARCH', + /** Extension returns file search results */ + SEARCH_RESULTS = 'SEARCH_RESULTS', + /** Extension requests focus on chat input */ + FOCUS_CHAT_INPUT = 'FOCUS_CHAT_INPUT', +} + +// ============================================================================ +// Message Payloads +// ============================================================================ + +/** Payload for WEBVIEW_READY message */ +export interface WebviewReadyPayload { + readonly version: string; +} + +/** Payload for STATUS_UPDATE message */ +export interface StatusUpdatePayload { + readonly status: ProcessStatus; + readonly message?: string; +} + +/** Payload for GET_STATUS message (empty payload) */ +export type GetStatusPayload = Record; + +/** Payload for ERROR message */ +export interface ErrorPayload { + readonly title: string; + readonly message: string; + readonly action?: { + readonly label: string; + readonly command: string; + }; +} + +/** Payload for SEND_MESSAGE message */ +export interface SendMessagePayload { + readonly content: string; + readonly messageId: string; + readonly responseId: string; + readonly contextChips?: readonly ContextChipData[]; +} + +/** Context chip data sent with messages (subset of ContextChip for extension) */ +export interface ContextChipData { + readonly filePath: string; + readonly range?: { + readonly startLine: number; + readonly endLine: number; + }; +} + +/** Payload for STREAM_TOKEN message */ +export interface StreamTokenPayload { + readonly messageId: string; + readonly token: string; + readonly done: boolean; +} + +/** Payload for GENERATION_COMPLETE message */ +export interface GenerationCompletePayload { + readonly messageId: string; +} + +/** Payload for STOP_GENERATION message (empty payload) */ +export type StopGenerationPayload = Record; + +/** Payload for GENERATION_CANCELLED message */ +export interface GenerationCancelledPayload { + readonly messageId: string; +} + +/** Payload for CHAT_HISTORY message */ +export interface ChatHistoryPayload { + readonly messages: readonly ChatMessage[]; +} + +/** Payload for OPEN_EXTERNAL_LINK message */ +export interface OpenExternalLinkPayload { + readonly url: string; +} + +// Session Management Payloads + +/** Payload for CREATE_SESSION message */ +export interface CreateSessionPayload { + readonly workingDirectory?: string; +} + +/** Payload for SESSION_CREATED message */ +export interface SessionCreatedPayload { + readonly session: SessionEntry; +} + +/** Payload for GET_SESSIONS message (empty payload) */ +export type GetSessionsPayload = Record; + +/** Payload for SESSIONS_LIST message */ +export interface SessionsListPayload { + readonly sessions: readonly SessionEntry[]; + readonly activeSessionId: string | null; +} + +/** Payload for SELECT_SESSION message */ +export interface SelectSessionPayload { + readonly sessionId: string; +} + +/** Payload for SESSION_LOADED message */ +export interface SessionLoadedPayload { + readonly sessionId: string; + readonly historyUnavailable?: boolean; +} + +/** Payload for HISTORY_MESSAGE message */ +export interface HistoryMessagePayload { + readonly message: ChatMessage; + readonly isReplay: true; +} + +/** Payload for HISTORY_COMPLETE message */ +export interface HistoryCompletePayload { + readonly sessionId: string; + readonly messageCount: number; +} + +// Version Status Payloads + +/** Payload for VERSION_STATUS message */ +export interface VersionStatusPayload { + readonly status: 'blocked_missing' | 'blocked_outdated' | 'compatible'; + readonly detectedVersion?: string; + readonly minimumVersion: string; + readonly installUrl?: string; + readonly updateUrl?: string; +} + +// Context Chip Payloads + +/** Payload for ADD_CONTEXT_CHIP message */ +export interface AddContextChipPayload { + readonly chip: ContextChip; +} + +/** Payload for FILE_SEARCH message */ +export interface FileSearchPayload { + readonly query: string; +} + +/** Payload for SEARCH_RESULTS message */ +export interface SearchResultsPayload { + readonly results: readonly FileSearchResult[]; +} + +/** Payload for FOCUS_CHAT_INPUT message (empty) */ +export type FocusChatInputPayload = Record; + +// ============================================================================ +// Message Type Mapping +// ============================================================================ + +/** Maps message types to their payload types */ +export interface WebviewMessagePayloads { + [WebviewMessageType.WEBVIEW_READY]: WebviewReadyPayload; + [WebviewMessageType.STATUS_UPDATE]: StatusUpdatePayload; + [WebviewMessageType.GET_STATUS]: GetStatusPayload; + [WebviewMessageType.ERROR]: ErrorPayload; + [WebviewMessageType.SEND_MESSAGE]: SendMessagePayload; + [WebviewMessageType.STREAM_TOKEN]: StreamTokenPayload; + [WebviewMessageType.GENERATION_COMPLETE]: GenerationCompletePayload; + [WebviewMessageType.STOP_GENERATION]: StopGenerationPayload; + [WebviewMessageType.GENERATION_CANCELLED]: GenerationCancelledPayload; + [WebviewMessageType.CHAT_HISTORY]: ChatHistoryPayload; + [WebviewMessageType.OPEN_EXTERNAL_LINK]: OpenExternalLinkPayload; + // Session Management + [WebviewMessageType.CREATE_SESSION]: CreateSessionPayload; + [WebviewMessageType.SESSION_CREATED]: SessionCreatedPayload; + [WebviewMessageType.GET_SESSIONS]: GetSessionsPayload; + [WebviewMessageType.SESSIONS_LIST]: SessionsListPayload; + [WebviewMessageType.SELECT_SESSION]: SelectSessionPayload; + [WebviewMessageType.SESSION_LOADED]: SessionLoadedPayload; + [WebviewMessageType.HISTORY_MESSAGE]: HistoryMessagePayload; + [WebviewMessageType.HISTORY_COMPLETE]: HistoryCompletePayload; + // Version Status + [WebviewMessageType.VERSION_STATUS]: VersionStatusPayload; + // Context Chips + [WebviewMessageType.ADD_CONTEXT_CHIP]: AddContextChipPayload; + [WebviewMessageType.FILE_SEARCH]: FileSearchPayload; + [WebviewMessageType.SEARCH_RESULTS]: SearchResultsPayload; + [WebviewMessageType.FOCUS_CHAT_INPUT]: FocusChatInputPayload; +} + +/** Generic webview message with typed payload */ +export interface WebviewMessage { + readonly type: T; + readonly payload: WebviewMessagePayloads[T]; +} + +/** Union of all possible webview messages */ +export type AnyWebviewMessage = { + [K in WebviewMessageType]: WebviewMessage; +}[WebviewMessageType]; + +// ============================================================================ +// Message Factory Functions +// ============================================================================ + +/** Create a WEBVIEW_READY message */ +export function createWebviewReadyMessage( + version: string +): WebviewMessage { + return { + type: WebviewMessageType.WEBVIEW_READY, + payload: { version }, + }; +} + +/** Create a STATUS_UPDATE message */ +export function createStatusUpdateMessage( + status: ProcessStatus, + message?: string +): WebviewMessage { + return { + type: WebviewMessageType.STATUS_UPDATE, + payload: { status, message }, + }; +} + +/** Create a GET_STATUS message */ +export function createGetStatusMessage(): WebviewMessage { + return { + type: WebviewMessageType.GET_STATUS, + payload: {}, + }; +} + +/** Create an ERROR message */ +export function createErrorMessage( + title: string, + message: string, + action?: { label: string; command: string } +): WebviewMessage { + return { + type: WebviewMessageType.ERROR, + payload: { title, message, action }, + }; +} + +/** Create a SEND_MESSAGE message */ +export function createSendMessageMessage( + content: string, + messageId: string, + responseId: string, + contextChips?: readonly ContextChipData[] +): WebviewMessage { + return { + type: WebviewMessageType.SEND_MESSAGE, + payload: { + content, + messageId, + responseId, + ...(contextChips && contextChips.length > 0 && { contextChips }), + }, + }; +} + +/** Create a STREAM_TOKEN message */ +export function createStreamTokenMessage( + messageId: string, + token: string, + done: boolean +): WebviewMessage { + return { + type: WebviewMessageType.STREAM_TOKEN, + payload: { messageId, token, done }, + }; +} + +/** Create a GENERATION_COMPLETE message */ +export function createGenerationCompleteMessage( + messageId: string +): WebviewMessage { + return { + type: WebviewMessageType.GENERATION_COMPLETE, + payload: { messageId }, + }; +} + +/** Create a STOP_GENERATION message */ +export function createStopGenerationMessage(): WebviewMessage { + return { + type: WebviewMessageType.STOP_GENERATION, + payload: {}, + }; +} + +/** Create a GENERATION_CANCELLED message */ +export function createGenerationCancelledMessage( + messageId: string +): WebviewMessage { + return { + type: WebviewMessageType.GENERATION_CANCELLED, + payload: { messageId }, + }; +} + +/** Create a CHAT_HISTORY message */ +export function createChatHistoryMessage( + messages: readonly ChatMessage[] +): WebviewMessage { + return { + type: WebviewMessageType.CHAT_HISTORY, + payload: { messages }, + }; +} + +/** Create an OPEN_EXTERNAL_LINK message */ +export function createOpenExternalLinkMessage( + url: string +): WebviewMessage { + return { + type: WebviewMessageType.OPEN_EXTERNAL_LINK, + payload: { url }, + }; +} + +// Session Management Factory Functions + +/** Create a CREATE_SESSION message */ +export function createCreateSessionMessage( + workingDirectory?: string +): WebviewMessage { + return { + type: WebviewMessageType.CREATE_SESSION, + payload: { workingDirectory }, + }; +} + +/** Create a SESSION_CREATED message */ +export function createSessionCreatedMessage( + session: SessionEntry +): WebviewMessage { + return { + type: WebviewMessageType.SESSION_CREATED, + payload: { session }, + }; +} + +/** Create a GET_SESSIONS message */ +export function createGetSessionsMessage(): WebviewMessage { + return { + type: WebviewMessageType.GET_SESSIONS, + payload: {}, + }; +} + +/** Create a SESSIONS_LIST message */ +export function createSessionsListMessage( + sessions: readonly SessionEntry[], + activeSessionId: string | null +): WebviewMessage { + return { + type: WebviewMessageType.SESSIONS_LIST, + payload: { sessions, activeSessionId }, + }; +} + +/** Create a SELECT_SESSION message */ +export function createSelectSessionMessage( + sessionId: string +): WebviewMessage { + return { + type: WebviewMessageType.SELECT_SESSION, + payload: { sessionId }, + }; +} + +/** Create a SESSION_LOADED message */ +export function createSessionLoadedMessage( + sessionId: string, + historyUnavailable?: boolean +): WebviewMessage { + return { + type: WebviewMessageType.SESSION_LOADED, + payload: { sessionId, historyUnavailable }, + }; +} + +/** Create a HISTORY_MESSAGE message */ +export function createHistoryMessage( + message: ChatMessage +): WebviewMessage { + return { + type: WebviewMessageType.HISTORY_MESSAGE, + payload: { message, isReplay: true }, + }; +} + +/** Create a HISTORY_COMPLETE message */ +export function createHistoryCompleteMessage( + sessionId: string, + messageCount: number +): WebviewMessage { + return { + type: WebviewMessageType.HISTORY_COMPLETE, + payload: { sessionId, messageCount }, + }; +} + +// Version Status Factory Functions + +/** Create a VERSION_STATUS message */ +export function createVersionStatusMessage( + status: VersionStatusPayload['status'], + minimumVersion: string, + options?: { + detectedVersion?: string; + installUrl?: string; + updateUrl?: string; + } +): WebviewMessage { + return { + type: WebviewMessageType.VERSION_STATUS, + payload: { + status, + minimumVersion, + detectedVersion: options?.detectedVersion, + installUrl: options?.installUrl, + updateUrl: options?.updateUrl, + }, + }; +} + +// Context Chip Factory Functions + +/** Create an ADD_CONTEXT_CHIP message */ +export function createAddContextChipMessage( + chip: ContextChip +): WebviewMessage { + return { + type: WebviewMessageType.ADD_CONTEXT_CHIP, + payload: { chip }, + }; +} + +/** Create a FILE_SEARCH message */ +export function createFileSearchMessage( + query: string +): WebviewMessage { + return { + type: WebviewMessageType.FILE_SEARCH, + payload: { query }, + }; +} + +/** Create a SEARCH_RESULTS message */ +export function createSearchResultsMessage( + results: readonly FileSearchResult[] +): WebviewMessage { + return { + type: WebviewMessageType.SEARCH_RESULTS, + payload: { results }, + }; +} + +/** Create a FOCUS_CHAT_INPUT message */ +export function createFocusChatInputMessage(): WebviewMessage { + return { + type: WebviewMessageType.FOCUS_CHAT_INPUT, + payload: {}, + }; +} + +// ============================================================================ +// Type Guards +// ============================================================================ + +/** Check if a message is of a specific type */ +export function isWebviewMessage( + message: unknown, + type: T +): message is WebviewMessage { + return ( + typeof message === 'object' && + message !== null && + 'type' in message && + (message as { type: unknown }).type === type + ); +} + +/** Check if message is WEBVIEW_READY */ +export function isWebviewReadyMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.WEBVIEW_READY); +} + +/** Check if message is STATUS_UPDATE */ +export function isStatusUpdateMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.STATUS_UPDATE); +} + +/** Check if message is GET_STATUS */ +export function isGetStatusMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.GET_STATUS); +} + +/** Check if message is ERROR */ +export function isErrorMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.ERROR); +} + +/** Check if message is SEND_MESSAGE */ +export function isSendMessageMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.SEND_MESSAGE); +} + +/** Check if message is STREAM_TOKEN */ +export function isStreamTokenMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.STREAM_TOKEN); +} + +/** Check if message is GENERATION_COMPLETE */ +export function isGenerationCompleteMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.GENERATION_COMPLETE); +} + +/** Check if message is STOP_GENERATION */ +export function isStopGenerationMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.STOP_GENERATION); +} + +/** Check if message is GENERATION_CANCELLED */ +export function isGenerationCancelledMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.GENERATION_CANCELLED); +} + +/** Check if message is CHAT_HISTORY */ +export function isChatHistoryMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.CHAT_HISTORY); +} + +/** Check if message is OPEN_EXTERNAL_LINK */ +export function isOpenExternalLinkMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.OPEN_EXTERNAL_LINK); +} + +// Session Management Type Guards + +/** Check if message is CREATE_SESSION */ +export function isCreateSessionMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.CREATE_SESSION); +} + +/** Check if message is SESSION_CREATED */ +export function isSessionCreatedMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.SESSION_CREATED); +} + +/** Check if message is GET_SESSIONS */ +export function isGetSessionsMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.GET_SESSIONS); +} + +/** Check if message is SESSIONS_LIST */ +export function isSessionsListMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.SESSIONS_LIST); +} + +/** Check if message is SELECT_SESSION */ +export function isSelectSessionMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.SELECT_SESSION); +} + +/** Check if message is SESSION_LOADED */ +export function isSessionLoadedMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.SESSION_LOADED); +} + +/** Check if message is HISTORY_MESSAGE */ +export function isHistoryMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.HISTORY_MESSAGE); +} + +/** Check if message is HISTORY_COMPLETE */ +export function isHistoryCompleteMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.HISTORY_COMPLETE); +} + +// Version Status Type Guards + +/** Check if message is VERSION_STATUS */ +export function isVersionStatusMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.VERSION_STATUS); +} + +// Context Chip Type Guards + +/** Check if message is ADD_CONTEXT_CHIP */ +export function isAddContextChipMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.ADD_CONTEXT_CHIP); +} + +/** Check if message is FILE_SEARCH */ +export function isFileSearchMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.FILE_SEARCH); +} + +/** Check if message is SEARCH_RESULTS */ +export function isSearchResultsMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.SEARCH_RESULTS); +} + +/** Check if message is FOCUS_CHAT_INPUT */ +export function isFocusChatInputMessage( + message: unknown +): message is WebviewMessage { + return isWebviewMessage(message, WebviewMessageType.FOCUS_CHAT_INPUT); +} diff --git a/src/shared/sessionTypes.ts b/src/shared/sessionTypes.ts new file mode 100644 index 0000000..464219e --- /dev/null +++ b/src/shared/sessionTypes.ts @@ -0,0 +1,132 @@ +/** + * Session management types for the VS Code Goose extension. + * Defines session storage and metadata structures. + */ + +/** Stored session metadata (persisted locally - minimal data only) */ +export interface SessionEntry { + readonly sessionId: string; + readonly title: string; + readonly cwd: string; + readonly createdAt: string; +} + +/** Session storage schema */ +export interface SessionStorageData { + readonly schemaVersion: 1; + readonly activeSessionId: string | null; + readonly sessions: readonly SessionEntry[]; +} + +/** Agent capabilities from ACP initialize response */ +export interface AgentCapabilities { + readonly loadSession: boolean; + readonly promptCapabilities: { + readonly image: boolean; + readonly audio: boolean; + readonly embeddedContext: boolean; + }; +} + +/** Session list grouped by date for UI */ +export interface GroupedSessions { + readonly label: string; + readonly sessions: readonly SessionEntry[]; +} + +/** Default capabilities - loadSession enabled for modern goose versions */ +export const DEFAULT_CAPABILITIES: AgentCapabilities = { + loadSession: true, + promptCapabilities: { + image: false, + audio: false, + embeddedContext: false, + }, +}; + +/** Generate a session title from the first message content */ +export function generateSessionTitle(firstMessage: string): string { + const maxLength = 50; + const trimmed = firstMessage.trim(); + + if (!trimmed) return 'New Session'; + if (trimmed.length <= maxLength) return trimmed; + + const truncated = trimmed.substring(0, maxLength); + const lastSpace = truncated.lastIndexOf(' '); + + return lastSpace > 20 ? truncated.substring(0, lastSpace) + '...' : truncated + '...'; +} + +/** Group sessions by date for display */ +export function groupSessionsByDate(sessions: readonly SessionEntry[]): GroupedSessions[] { + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000); + + const groups = new Map(); + + for (const session of sessions) { + const sessionDate = new Date(session.createdAt); + const sessionDay = new Date( + sessionDate.getFullYear(), + sessionDate.getMonth(), + sessionDate.getDate() + ); + + let label: string; + if (sessionDay.getTime() === today.getTime()) { + label = 'Today'; + } else if (sessionDay.getTime() === yesterday.getTime()) { + label = 'Yesterday'; + } else { + label = sessionDay.toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + }); + } + + const existing = groups.get(label); + if (existing) { + existing.push(session); + } else { + groups.set(label, [session]); + } + } + + const sortedEntries = Array.from(groups.entries()).sort(([a], [b]) => { + if (a === 'Today') return -1; + if (b === 'Today') return 1; + if (a === 'Yesterday') return -1; + if (b === 'Yesterday') return 1; + return new Date(b).getTime() - new Date(a).getTime(); + }); + + return sortedEntries.map(([label, groupSessions]) => ({ + label, + sessions: [...groupSessions].sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ), + })); +} + +/** Format a timestamp for display */ +export function formatSessionTime(createdAt: string): string { + const date = new Date(createdAt); + return date.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + hour12: true, + }); +} + +/** Truncate a path for display */ +export function truncatePath(path: string, maxLength: number = 30): string { + if (path.length <= maxLength) return path; + + const parts = path.split('/').filter(Boolean); + if (parts.length <= 2) return '.../' + parts.join('/'); + + return '.../' + parts.slice(-2).join('/'); +} diff --git a/src/shared/types.test.ts b/src/shared/types.test.ts new file mode 100644 index 0000000..dcf6ec1 --- /dev/null +++ b/src/shared/types.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, test } from 'bun:test'; +import { LogLevel, parseLogLevel } from './types'; + +describe('parseLogLevel', () => { + describe('known levels parse correctly', () => { + test('parses debug level', () => { + expect(parseLogLevel('debug')).toBe(LogLevel.DEBUG); + }); + + test('parses info level', () => { + expect(parseLogLevel('info')).toBe(LogLevel.INFO); + }); + + test('parses warn level', () => { + expect(parseLogLevel('warn')).toBe(LogLevel.WARN); + }); + + test('parses error level', () => { + expect(parseLogLevel('error')).toBe(LogLevel.ERROR); + }); + }); + + describe('case insensitive parsing', () => { + test('parses uppercase DEBUG', () => { + expect(parseLogLevel('DEBUG')).toBe(LogLevel.DEBUG); + }); + + test('parses uppercase INFO', () => { + expect(parseLogLevel('INFO')).toBe(LogLevel.INFO); + }); + + test('parses uppercase WARN', () => { + expect(parseLogLevel('WARN')).toBe(LogLevel.WARN); + }); + + test('parses uppercase ERROR', () => { + expect(parseLogLevel('ERROR')).toBe(LogLevel.ERROR); + }); + + test('parses mixed case', () => { + expect(parseLogLevel('Debug')).toBe(LogLevel.DEBUG); + expect(parseLogLevel('Info')).toBe(LogLevel.INFO); + expect(parseLogLevel('Warn')).toBe(LogLevel.WARN); + expect(parseLogLevel('Error')).toBe(LogLevel.ERROR); + }); + }); + + describe('unknown strings return INFO as default', () => { + test('returns INFO for unknown string', () => { + expect(parseLogLevel('verbose')).toBe(LogLevel.INFO); + }); + + test('returns INFO for garbage input', () => { + expect(parseLogLevel('garbage')).toBe(LogLevel.INFO); + }); + + test('returns INFO for typos', () => { + expect(parseLogLevel('debg')).toBe(LogLevel.INFO); + expect(parseLogLevel('warning')).toBe(LogLevel.INFO); + expect(parseLogLevel('err')).toBe(LogLevel.INFO); + }); + }); + + describe('empty string returns INFO as default', () => { + test('returns INFO for empty string', () => { + expect(parseLogLevel('')).toBe(LogLevel.INFO); + }); + }); +}); diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 0000000..a7c9225 --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,155 @@ +/** + * Common types and interfaces shared between extension and webview. + */ + +/** Status of the goose subprocess */ +export enum ProcessStatus { + STOPPED = 'stopped', + STARTING = 'starting', + RUNNING = 'running', + ERROR = 'error', +} + +/** Log levels for the logger */ +export enum LogLevel { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, +} + +/** Parse log level from string (e.g., from VS Code settings) */ +export function parseLogLevel(level: string): LogLevel { + switch (level.toLowerCase()) { + case 'debug': + return LogLevel.DEBUG; + case 'info': + return LogLevel.INFO; + case 'warn': + return LogLevel.WARN; + case 'error': + return LogLevel.ERROR; + default: + return LogLevel.INFO; + } +} + +/** Convert log level to string */ +export function logLevelToString(level: LogLevel): string { + switch (level) { + case LogLevel.DEBUG: + return 'DEBUG'; + case LogLevel.INFO: + return 'INFO'; + case LogLevel.WARN: + return 'WARN'; + case LogLevel.ERROR: + return 'ERROR'; + } +} + +// ============================================================================ +// JSON-RPC Types +// ============================================================================ + +/** JSON-RPC 2.0 request */ +export interface JsonRpcRequest { + readonly jsonrpc: '2.0'; + readonly id: number; + readonly method: string; + readonly params?: unknown; +} + +/** JSON-RPC 2.0 response */ +export interface JsonRpcResponse { + readonly jsonrpc: '2.0'; + readonly id: number; + readonly result?: unknown; + readonly error?: JsonRpcResponseError; +} + +/** JSON-RPC 2.0 error object in response */ +export interface JsonRpcResponseError { + readonly code: number; + readonly message: string; + readonly data?: unknown; +} + +/** JSON-RPC 2.0 notification (no id, no response expected) */ +export interface JsonRpcNotification { + readonly jsonrpc: '2.0'; + readonly method: string; + readonly params?: unknown; +} + +/** Pending request tracking for matching responses to requests */ +export interface PendingRequest { + readonly id: number; + readonly method: string; + readonly resolve: (value: T) => void; + readonly reject: (error: Error) => void; + readonly timer: ReturnType; +} + +// ============================================================================ +// Binary Discovery Types +// ============================================================================ + +/** Configuration for binary discovery */ +export interface BinaryDiscoveryConfig { + readonly userConfiguredPath: string | undefined; + readonly platform: NodeJS.Platform; + readonly env: NodeJS.ProcessEnv; + readonly homeDir: string; +} + +// ============================================================================ +// Chat UI Types +// ============================================================================ + +/** Role of a chat message */ +export enum MessageRole { + USER = 'user', + ASSISTANT = 'assistant', + ERROR = 'error', +} + +/** Status of a chat message */ +export enum MessageStatus { + PENDING = 'pending', + STREAMING = 'streaming', + COMPLETE = 'complete', + CANCELLED = 'cancelled', + ERROR = 'error', +} + +/** Attached context reference in a message (used for both input and history) */ +export interface MessageContext { + readonly filePath: string; + readonly fileName: string; + readonly range?: { + readonly startLine: number; + readonly endLine: number; + }; + readonly content?: string; // File content (present in history, absent in live messages) +} + +/** Chat message structure */ +export interface ChatMessage { + readonly id: string; + readonly role: MessageRole; + readonly content: string; + readonly timestamp?: Date; // Optional - undefined for history messages loaded from server + readonly status: MessageStatus; + readonly originalContent?: string; + readonly context?: readonly MessageContext[]; // Attached file/resource references +} + +/** Chat UI state */ +export interface ChatState { + readonly messages: readonly ChatMessage[]; + readonly isGenerating: boolean; + readonly currentResponseId: string | null; + readonly inputDraft: string; + readonly focusedMessageIndex: number | null; +} diff --git a/src/test/activation.test.ts b/src/test/activation.test.ts deleted file mode 100644 index 5095509..0000000 --- a/src/test/activation.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; - -suite('Packaged Extension Activation Test Suite', () => { - test('Extension should activate successfully', async () => { - console.log('Running activation test...'); - - // Find the extension by its ID (name in package.json) - const extensionId = 'block.vscode-goose'; // Make sure this matches package.json publisher.name - const extension = vscode.extensions.getExtension(extensionId); - - // 1. Check if the extension is present - if (!extension) { - // Log available extensions if not found - const availableExtensions = vscode.extensions.all.map((ext: vscode.Extension) => ext.id).join(', '); - console.error(`Available extensions: ${availableExtensions}`); - assert.fail(`Extension with ID '${extensionId}' not found.`); - } - console.log(`Extension '${extensionId}' found.`); - - // 2. Check if it's already active (it might activate on startup) - if (extension.isActive) { - console.log(`Extension '${extensionId}' is already active.`); - assert.ok(true, 'Extension was already active.'); - return; // Test passes if already active - } - - // 3. Attempt to activate the extension (handles cases where it might already be active) - console.log(`Ensuring extension '${extensionId}' is active...`); - try { - // VS Code's activate() should be idempotent or handle multiple calls gracefully. - // We call it to ensure activation completes if VS Code didn't auto-activate it fully yet. - await extension.activate(); - console.log(`Extension '${extensionId}' activation call completed.`); - } catch (err: any) { - // Check if the error is the specific "already registered" error. - // This indicates VS Code likely auto-activated it successfully before our explicit call. - const alreadyRegisteredError = "already registered"; - if (err.message && err.message.includes(alreadyRegisteredError)) { - console.warn(`Caught expected error during explicit activation (likely already active): ${err.message}`); - // Treat this specific error as acceptable, as it means activation already happened. - } else { - // Any other error during activation is unexpected. - console.error(`Unexpected error activating extension '${extensionId}':`, err); - assert.fail(`Failed to activate extension '${extensionId}': ${err}`); - } - } - - // 4. Final check: Assert that the extension is now definitively active - assert.ok(extension.isActive, 'Extension should be active after activation attempt.'); - console.log(`Extension '${extensionId}' confirmed active.`); - - // 5. Verify core bundled dependency (yaml) is accessible - console.log(`Verifying bundled 'yaml' dependency...`); - try { - // Attempt to require and use the yaml module directly - // This should work if esbuild bundled it correctly - const YAML = require('yaml'); - const testYaml = 'key: value'; - const parsed = YAML.parse(testYaml); - assert.strictEqual(parsed.key, 'value', 'YAML parsing failed'); - console.log(`Successfully required and used 'yaml' module.`); - } catch (err) { - console.error(`Failed to require or use bundled 'yaml' module:`, err); - assert.fail(`Bundled 'yaml' module is not functional: ${err}`); - } - }); -}); diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts deleted file mode 100644 index f6c4c56..0000000 --- a/src/test/extension.test.ts +++ /dev/null @@ -1,458 +0,0 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import * as sinon from 'sinon'; -import { - handleAskAboutSelectionCommand, // <-- Import the refactored handler - GooseViewProvider // <-- Need for type hinting the mock provider -} from '../extension'; // Adjust path as necessary -import { CodeReferenceManager, CodeReference } from '../utils/codeReferenceManager'; // CodeReference is here -import { MessageType } from '../common-types'; // MessageType is here, WebviewMessage is not a direct export -// import * as myExtension from '../extension'; // Already imported necessary parts -import { setupTestEnvironment, getTestBinaryPathResolver } from './testUtils'; -// import { CodeReferenceManager, CodeReference } from '../utils/codeReferenceManager'; -// import { GooseViewProvider } from '../extension'; -// import { MessageType } from '../common-types'; -import * as path from 'path'; - -// Define constant for the limit used in extension.ts -const SELECTION_LINE_LIMIT_FOR_PREPEND = 100; - -suite('Extension Test Suite', () => { - // Declare variables in the suite scope - let testEnv: ReturnType; - let getBinaryPathStub: sinon.SinonStub; - let serverManagerStub: sinon.SinonStub; - let showInformationMessageStub: sinon.SinonStub; - let registerWebviewProviderStub: sinon.SinonStub; - let capturedProvider: GooseViewProvider | undefined; - let postMessageSpy: sinon.SinonSpy | sinon.SinonStub; // Can be spy or stub - let activeEditorStub: sinon.SinonStub | undefined; - let mockContext: vscode.ExtensionContext; - let registerCommandStub: sinon.SinonStub; - let executeCommandStub: sinon.SinonStub; - let getInstanceStub: sinon.SinonStub; // Stub for CodeReferenceManager.getInstance - let mockCodeRefManager: { // Mock object returned by getInstanceStub - getCodeReferenceFromSelection: sinon.SinonStub; - getCodeReferenceForEntireFile: sinon.SinonStub; - formatCodeReferenceForChat: sinon.SinonStub; - getCodeReferenceDisplayString: sinon.SinonStub; - }; - let providerPostMessageSpy: sinon.SinonStub; - let getCodeReferenceFromSelectionStub: sinon.SinonStub; - let getCodeReferenceForEntireFileStub: sinon.SinonStub; - - // Mock editor/document/selection setup helper - const setupMockEditor = ( - selectionText: string | null, - fileText: string, - selectionRange?: vscode.Range, - selectionIsEmpty = false, - selectionIsWhitespace = false, - fileIsEmpty = false, - fileIsWhitespace = false - ) => { - // Restore previous stub if exists - if (activeEditorStub) { - activeEditorStub.restore(); - } - - let mockSelection: vscode.Selection; - const mockFilePath = 'mock/test.ts'; // Define mock path - - if (selectionRange) { - // Use provided range - mockSelection = new vscode.Selection(selectionRange.start, selectionRange.end); - } else if (selectionText === null || selectionIsEmpty) { - // Simulate no selection or programmatically empty selection - const position = new vscode.Position(0, 0); - mockSelection = new vscode.Selection(position, position); - } else { - // Simulate a non-empty selection - const startPosition = new vscode.Position(0, 0); - // FIX: Use Position constructor for the second argument - const endPosition = new vscode.Position(0, selectionText.length); - mockSelection = new vscode.Selection(startPosition, endPosition); - } - - // Mock Document - const mockDocument: Partial = { - uri: vscode.Uri.file(mockFilePath), - fileName: mockFilePath, - getText: (range?: vscode.Range) => { - if (range) { - if (range.isEqual(mockSelection)) { - return selectionIsWhitespace ? ' \t\n ' : (selectionText ?? ''); - } - } - return fileIsWhitespace ? ' \t \n ' : fileText; - }, - lineCount: fileText.split('\n').length, - languageId: 'typescript', - isDirty: false, - isClosed: false, - isUntitled: false, - eol: vscode.EndOfLine.LF, - version: 1, - validatePosition: (pos) => pos, // Simplified validation - validateRange: (range) => range, // Simplified validation - positionAt: (offset) => new vscode.Position(0, offset), // Simplified - offsetAt: (pos) => pos.character, // Simplified - // FIX: Handle both number and Position input for line, like the real API - lineAt: (lineOrPosition: number | vscode.Position): vscode.TextLine => { - const lineNumber = typeof lineOrPosition === 'number' ? lineOrPosition : lineOrPosition.line; - const lineText = fileText.split('\n')[lineNumber] ?? ''; - const range = new vscode.Range(lineNumber, 0, lineNumber, lineText.length); - const rangeIncludingLineBreak = new vscode.Range(lineNumber, 0, lineNumber + 1, 0); // Approx - const firstNonWhitespace = lineText.search(/\S|$/); - return { - lineNumber: lineNumber, - text: lineText, - range: range, - rangeIncludingLineBreak: rangeIncludingLineBreak, - firstNonWhitespaceCharacterIndex: firstNonWhitespace === -1 ? 0 : firstNonWhitespace, // Handle empty/whitespace lines - isEmptyOrWhitespace: lineText.trim().length === 0 - } as vscode.TextLine; - }, - save: async () => true, - getWordRangeAtPosition: (pos) => new vscode.Range(pos, pos) // Simplified - - }; - - // Mock References for CodeReferenceManager stubs - // FIX: Use selectedText, add id and filePath - const mockSelectionRef: CodeReference | null = - (selectionText && !selectionIsEmpty && !selectionIsWhitespace) - ? { id: 'mock-selection-ref', filePath: mockFilePath, selectedText: selectionText, fileName: 'test.ts', startLine: 1, endLine: 1, languageId: 'typescript' } - : null; - - // FIX: Use selectedText, add id and filePath - const mockFileRef: CodeReference | null = - (!fileIsEmpty && !fileIsWhitespace) - ? { id: 'mock-file-ref', filePath: mockFilePath, selectedText: fileText, fileName: 'test.ts', startLine: 1, endLine: mockDocument.lineCount || 1, languageId: 'typescript' } - : null; - - // Adjust stubs to return the new mocks - mockCodeRefManager.getCodeReferenceFromSelection.callsFake(() => { - // Simulate the check for whitespace/empty that the real method does - const editor = vscode.window.activeTextEditor; - if (!editor || editor.selection.isEmpty || (selectionText && selectionText.trim() === '')) { return null; } - return mockSelectionRef; - }); - mockCodeRefManager.getCodeReferenceForEntireFile.callsFake((doc: vscode.TextDocument) => { - // Simulate the check for whitespace/empty that the real method does - const text = doc.getText(); // Gets full text - if (!text || text.trim() === '') { return null; } - return mockFileRef; - }); - - const mockEditor: Partial = { - document: mockDocument as vscode.TextDocument, - selection: mockSelection, - // Add other properties/methods if needed by the command - }; - - activeEditorStub = testEnv.sandbox.stub(vscode.window, 'activeTextEditor').value(mockEditor as vscode.TextEditor); - }; - - // Runs ONCE before all tests in this suite - suiteSetup(async () => { - testEnv = setupTestEnvironment(); - // Stub VS Code API functions - registerCommandStub = testEnv.sandbox.stub(vscode.commands, 'registerCommand'); - executeCommandStub = testEnv.sandbox.stub(vscode.commands, 'executeCommand'); - showInformationMessageStub = testEnv.sandbox.stub(vscode.window, 'showInformationMessage'); - - // --- Stub CodeReferenceManager.getInstance --- VVV - // Create stubs for the methods first - const getSelectionStub = testEnv.sandbox.stub(); - const getFileStub = testEnv.sandbox.stub(); - const formatCodeReferenceForChatStub = testEnv.sandbox.stub(); - const getCodeReferenceDisplayStringStub = testEnv.sandbox.stub(); - mockCodeRefManager = { - getCodeReferenceFromSelection: getSelectionStub, - getCodeReferenceForEntireFile: getFileStub, - formatCodeReferenceForChat: formatCodeReferenceForChatStub, - getCodeReferenceDisplayString: getCodeReferenceDisplayStringStub, - }; - // Stub getInstance to return our mock object - getInstanceStub = testEnv.sandbox.stub(CodeReferenceManager, 'getInstance').returns(mockCodeRefManager); - // --- Stub CodeReferenceManager.getInstance --- ^^^^ - - // Stub and capture the provider - registerWebviewProviderStub = testEnv.sandbox.stub(vscode.window, 'registerWebviewViewProvider'); - registerWebviewProviderStub.callsFake((_viewId, provider, _options) => { - capturedProvider = provider as GooseViewProvider; - // Restore any previous spy/stub assigned to postMessageSpy first - if (postMessageSpy && typeof postMessageSpy.restore === 'function') { - postMessageSpy.restore(); - } - // Now, attempt to create the spy on the captured provider - if (capturedProvider && typeof capturedProvider.postMessage === 'function') { - postMessageSpy = testEnv.sandbox.spy(capturedProvider, 'postMessage'); - console.log('Successfully spied on postMessage.'); - } else { - // If spying fails, assign a stub and log warning - console.warn('Warning: Could not find postMessage on captured provider. Assigning stub to postMessageSpy.'); - postMessageSpy = testEnv.sandbox.stub(); - } - return { dispose: sinon.stub() }; - }); - - // Create Mock Context (needed for activation) - mockContext = { - subscriptions: [], - extensionPath: '/test/extension', - extensionUri: vscode.Uri.file('/test/extension'), - asAbsolutePath: (p: string) => path.join('/test/extension', p), - storageUri: vscode.Uri.file('/test/storage'), // Provide a valid URI - globalState: { get: testEnv.sandbox.stub(), update: testEnv.sandbox.stub(), setKeysForSync: testEnv.sandbox.stub() } as any, - workspaceState: { get: testEnv.sandbox.stub(), update: testEnv.sandbox.stub() } as any, - secrets: { get: testEnv.sandbox.stub(), store: testEnv.sandbox.stub(), delete: testEnv.sandbox.stub(), onDidChange: testEnv.sandbox.stub().returns({ dispose: () => { } }) } as any, - extensionMode: vscode.ExtensionMode.Test, // Use Test mode - globalStorageUri: vscode.Uri.file('/test/globalStorage'), - logUri: vscode.Uri.file('/test/logs'), - logPath: '/test/logs', - environmentVariableCollection: {} as any, // Add missing properties if needed by activate - extension: {} as any, - storagePath: '/test/storage_obsolete', // deprecated but might be needed - globalStoragePath: '/test/globalStorage_obsolete', // deprecated - languageModelAccessInformation: { - onDidChange: testEnv.sandbox.stub().returns({ dispose: () => { } }), - canSendRequest: testEnv.sandbox.stub().returns(true) // Mock implementation - } - }; - - }); - - // Runs ONCE after all tests in this suite - suiteTeardown(() => { - if (testEnv) { - testEnv.sandbox.restore(); // FIX: Use sandbox.restore() - } - // Explicitly restore static stub if sandbox didn't catch it (shouldn't be necessary but safe) - if (getInstanceStub && typeof getInstanceStub.restore === 'function') { - getInstanceStub.restore(); - } - }); - - // --- Hooks per test --- - - // Runs BEFORE EACH test - setup(() => { - // Reset history of mocks/stubs that persist across tests - registerCommandStub.resetHistory(); - executeCommandStub.resetHistory(); - showInformationMessageStub.resetHistory(); - - // Reset stubs on the mock CodeReferenceManager - mockCodeRefManager.getCodeReferenceFromSelection.resetHistory(); - mockCodeRefManager.getCodeReferenceFromSelection.resolves(null); // Default return value - mockCodeRefManager.getCodeReferenceForEntireFile.resetHistory(); - mockCodeRefManager.getCodeReferenceForEntireFile.resolves(null); // Default return value - mockCodeRefManager.formatCodeReferenceForChat.resetHistory(); - mockCodeRefManager.formatCodeReferenceForChat.resolves('Formatted ref'); // Default return value - mockCodeRefManager.getCodeReferenceDisplayString.resetHistory(); - mockCodeRefManager.getCodeReferenceDisplayString.resolves('Display ref'); // Default return value - - // Reset the sendMessageToWebview spy/stub history - if (postMessageSpy && typeof postMessageSpy.resetHistory === 'function') { - postMessageSpy.resetHistory(); - } - - // Reset active editor stub (will be set by setupMockEditor if needed) - if (activeEditorStub) { - activeEditorStub.restore(); - activeEditorStub = undefined; - } - }); - - // Runs AFTER EACH test - teardown(() => { - // Sandbox automatically restores stubs created within the 'setup' scope - // But need to restore activeEditorStub if setupMockEditor was called - if (activeEditorStub) { - activeEditorStub.restore(); - activeEditorStub = undefined; - } - // No need to restore suite-level stubs here (CodeReferenceManager, showInfoMsg etc.) - // Sandbox handles those in suiteTeardown - }); - - // --- Tests --- - suite('goose.askAboutSelection Command Tests', () => { - let mockProviderInstance: Partial; - let mockCodeRefManager: sinon.SinonStubbedInstance; // Use SinonStubbedInstance - let providerPostMessageSpy: sinon.SinonStub; - let getCodeReferenceFromSelectionStub: sinon.SinonStub; - let getCodeReferenceForEntireFileStub: sinon.SinonStub; - - // Runs before each test in THIS suite - setup(() => { - // Reset mocks before each test to ensure isolation - mockProviderInstance = { - postMessage: testEnv.sandbox.stub(), - // Add other methods/properties of GooseViewProvider if they are called by handleAskAboutSelectionCommand - // and not already handled by general stubs (like _view or _extensionUri if needed). - }; - - // Create a stubbed instance of CodeReferenceManager for each test - // We can't easily stub a true singleton's methods across tests without them interfering. - // So, for testing, we'll create a fresh stub object that *looks* like CodeReferenceManager. - mockCodeRefManager = testEnv.sandbox.createStubInstance(CodeReferenceManager); - - // Assign the stubs to our suite-level variables so tests can use them for `returns` etc. - getCodeReferenceFromSelectionStub = mockCodeRefManager.getCodeReferenceFromSelection; - getCodeReferenceForEntireFileStub = mockCodeRefManager.getCodeReferenceForEntireFile; - - // Spy on the postMessage of the mock provider - providerPostMessageSpy = mockProviderInstance.postMessage as sinon.SinonStub; - - if (providerPostMessageSpy && typeof providerPostMessageSpy.resetHistory === 'function') { - providerPostMessageSpy.resetHistory(); - } else { - console.warn('Could not reset providerPostMessageSpy'); - } - - // Ensure activeTextEditor is stubbed for each test in this suite - // The setupMockEditor helper will be called by each test as needed. - }); - - test('should show message if file is empty (no selection)', async () => { - setupMockEditor(null, '', undefined, true, false, true); - // Execute the command handler directly - await handleAskAboutSelectionCommand(mockProviderInstance as GooseViewProvider, mockCodeRefManager); - sinon.assert.calledWith(showInformationMessageStub, 'Active file is empty or contains only whitespace.'); - sinon.assert.notCalled(getCodeReferenceForEntireFileStub); // Should not try to get ref - sinon.assert.notCalled(providerPostMessageSpy); // Should not send any message - }); - - test('should show message if file is only whitespace (no selection)', async () => { - setupMockEditor(null, ' \n\t ', undefined, true, false, false, true); - await handleAskAboutSelectionCommand(mockProviderInstance as GooseViewProvider, mockCodeRefManager); - sinon.assert.calledWith(showInformationMessageStub, 'Active file is empty or contains only whitespace.'); - sinon.assert.notCalled(getCodeReferenceForEntireFileStub); - sinon.assert.notCalled(providerPostMessageSpy); - }); - - test('should send PREPARE_MESSAGE_WITH_CODE for valid file < limit (no selection)', async () => { - const fileContent = 'const hello = "world";\nconsole.log(hello);'; - const expectedPayload = { - content: fileContent, - fileName: 'test.ts', - languageId: 'typescript', - startLine: 1, - endLine: 2 - }; - setupMockEditor(null, fileContent, undefined, true, false, false, false); - - await handleAskAboutSelectionCommand(mockProviderInstance as GooseViewProvider, mockCodeRefManager); - - sinon.assert.notCalled(getCodeReferenceForEntireFileStub); // Should not use manager for small files - sinon.assert.calledTwice(providerPostMessageSpy); - sinon.assert.calledWith(providerPostMessageSpy, sinon.match({ - command: MessageType.PREPARE_MESSAGE_WITH_CODE, - payload: sinon.match(expectedPayload) - })); - sinon.assert.calledWith(providerPostMessageSpy, sinon.match({ - command: MessageType.FOCUS_CHAT_INPUT - })); - }); - - test('should send ADD_CODE_REFERENCE for valid file >= limit (no selection)', async () => { - const fileContent = 'const hello = "world";\nconsole.log(hello);\n'.repeat(50); - const mockFileReference: CodeReference = { - id: 'mock-file-ref', - filePath: 'mock/test.ts', - selectedText: fileContent, // Entire file content - fileName: 'test.ts', - startLine: 1, - endLine: 100, - languageId: 'typescript' - }; - setupMockEditor(null, fileContent, undefined, true, false, false, false); - getCodeReferenceForEntireFileStub.returns(mockFileReference); - - await handleAskAboutSelectionCommand(mockProviderInstance as GooseViewProvider, mockCodeRefManager); - - sinon.assert.calledOnce(getCodeReferenceForEntireFileStub); - sinon.assert.calledTwice(providerPostMessageSpy); - sinon.assert.calledWith(providerPostMessageSpy, sinon.match({ - command: MessageType.ADD_CODE_REFERENCE, - codeReference: mockFileReference - })); - sinon.assert.calledWith(providerPostMessageSpy, sinon.match({ - command: MessageType.FOCUS_CHAT_INPUT - })); - }); - - test('should show message if selection is only whitespace', async () => { - setupMockEditor(' \t\n ', 'Some file content', undefined, false, true); - await handleAskAboutSelectionCommand(mockProviderInstance as GooseViewProvider, mockCodeRefManager); - sinon.assert.calledWith(showInformationMessageStub, 'Selected text is empty or contains only whitespace.'); - sinon.assert.notCalled(getCodeReferenceFromSelectionStub); - sinon.assert.notCalled(providerPostMessageSpy); - }); - - test('should send PREPARE_MESSAGE_WITH_CODE for valid selection < limit', async () => { - const selectionText = 'console.log("selected");'; - // Update expectedPayload to match the CodeReference structure - const expectedPayload = { - id: sinon.match.string, // ID is generated with Date.now() - filePath: '/mock/test.ts', // fsPath usually prepends a slash for Uri.file - fileName: 'test.ts', - selectedText: selectionText, // Changed from content - languageId: 'typescript', - startLine: 5, - endLine: 5 - }; - const testSelectionRange = new vscode.Selection( - new vscode.Position(4, 0), // start Line 5 (0-based) - new vscode.Position(4, selectionText.length) // end Line 5 - ); - setupMockEditor(selectionText, 'File content\n'.repeat(10), testSelectionRange, false, false); - - await handleAskAboutSelectionCommand(mockProviderInstance as GooseViewProvider, mockCodeRefManager); - - sinon.assert.notCalled(getCodeReferenceFromSelectionStub); // Manager not used for small selections - sinon.assert.calledTwice(providerPostMessageSpy); - sinon.assert.calledWith(providerPostMessageSpy, sinon.match({ - command: MessageType.PREPARE_MESSAGE_WITH_CODE, - payload: sinon.match(expectedPayload) - })); - sinon.assert.calledWith(providerPostMessageSpy, sinon.match({ - command: MessageType.FOCUS_CHAT_INPUT - })); - }); - - test('should send ADD_CODE_REFERENCE for valid selection >= limit', async () => { - const selectionText = 'const hello = "world";\nconsole.log(hello);\n'.repeat(50); - const mockSelectionReference: CodeReference = { - id: 'mock-selection-ref', - filePath: 'mock/test.ts', - selectedText: selectionText, - fileName: 'test.ts', - startLine: 1, - endLine: 100, - languageId: 'typescript' - }; - const largeSelectionRange = new vscode.Selection( - new vscode.Position(0, 0), // start Line 1 (0-based) - new vscode.Position(99, selectionText.length) // end Line 100 - ); - setupMockEditor(selectionText, 'File content\n'.repeat(10), largeSelectionRange, false, false); - getCodeReferenceFromSelectionStub.returns(mockSelectionReference); - - await handleAskAboutSelectionCommand(mockProviderInstance as GooseViewProvider, mockCodeRefManager); - - sinon.assert.calledOnce(getCodeReferenceFromSelectionStub); - sinon.assert.calledTwice(providerPostMessageSpy); - sinon.assert.calledWith(providerPostMessageSpy, sinon.match({ - command: MessageType.ADD_CODE_REFERENCE, - codeReference: mockSelectionReference - })); - sinon.assert.calledWith(providerPostMessageSpy, sinon.match({ - command: MessageType.FOCUS_CHAT_INPUT - })); - }); - }); // End of 'goose.askAboutSelection Command Tests' suite -}); // End of 'Goose Extension Tests' suite diff --git a/src/test/mocks/mocks.test.ts b/src/test/mocks/mocks.test.ts new file mode 100644 index 0000000..85c27e8 --- /dev/null +++ b/src/test/mocks/mocks.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, test } from 'bun:test'; +import { createMockStreams } from './streams'; +import { createMockMemento } from './vscode'; + +describe('createMockMemento', () => { + test('returns undefined for missing key', () => { + const memento = createMockMemento(); + expect(memento.get('missing')).toBeUndefined(); + }); + + test('returns default value for missing key', () => { + const memento = createMockMemento(); + expect(memento.get('missing', 'default')).toBe('default'); + }); + + test('stores and retrieves value', async () => { + const memento = createMockMemento(); + await memento.update('key', { value: 'test' }); + expect(memento.get('key')).toEqual({ value: 'test' }); + }); + + test('deletes key when value is undefined', async () => { + const memento = createMockMemento(); + await memento.update('key', 'value'); + expect(memento.get('key')).toBe('value'); + + await memento.update('key', undefined); + expect(memento.get('key')).toBeUndefined(); + }); + + test('returns stored keys', async () => { + const memento = createMockMemento(); + await memento.update('a', 1); + await memento.update('b', 2); + expect(memento.keys()).toEqual(['a', 'b']); + }); +}); + +describe('createMockStreams', () => { + test('captures written data', () => { + const { stdin, written } = createMockStreams(); + stdin.write('test data'); + expect(written).toContain('test data'); + }); + + test('pushResponse adds data to stdout', done => { + const { stdout, pushResponse } = createMockStreams(); + + stdout.on('data', (chunk: Buffer) => { + expect(chunk.toString()).toBe('response\n'); + done(); + }); + + pushResponse('response'); + }); + + test('close ends the stdout stream', done => { + const { stdout, close } = createMockStreams(); + + stdout.on('end', () => { + done(); + }); + + // Need to consume data for 'end' to fire + // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional noop for stream consumption + stdout.on('data', () => {}); + close(); + }); +}); diff --git a/src/test/mocks/streams.ts b/src/test/mocks/streams.ts new file mode 100644 index 0000000..2b6fe64 --- /dev/null +++ b/src/test/mocks/streams.ts @@ -0,0 +1,72 @@ +/** + * Mock utilities for stream-based testing + * Used by JSON-RPC client tests and subprocess integration tests + */ + +import { Readable, Writable } from 'stream'; + +/** + * Creates mock stdin/stdout streams for testing subprocess communication. + * Captures written data and allows pushing responses to simulate subprocess output. + * + * @example + * ```typescript + * const { stdin, stdout, written, pushResponse, close } = createMockStreams(); + * + * // Write to stdin (simulates extension -> subprocess) + * stdin.write('{"jsonrpc":"2.0","method":"test"}'); + * + * // Check what was written + * expect(written).toContain('{"jsonrpc":"2.0","method":"test"}'); + * + * // Push response (simulates subprocess -> extension) + * pushResponse('{"jsonrpc":"2.0","result":"ok"}'); + * + * // Close stream when done + * close(); + * ``` + */ +export function createMockStreams(): MockStreams { + const written: string[] = []; + + const stdin = new Writable({ + write(chunk, _encoding, callback) { + written.push(chunk.toString()); + callback(); + }, + }); + + const stdout = new Readable({ + read() { + // No-op: data is pushed via pushResponse + }, + }); + + return { + stdin, + stdout, + written, + pushResponse: (data: string): void => { + stdout.push(data + '\n'); + }, + close: (): void => { + stdout.push(null); + }, + }; +} + +/** + * Mock streams interface returned by createMockStreams + */ +export interface MockStreams { + /** Writable stream simulating subprocess stdin */ + readonly stdin: Writable; + /** Readable stream simulating subprocess stdout */ + readonly stdout: Readable; + /** Array of all strings written to stdin */ + readonly written: string[]; + /** Push a response line to stdout (adds newline automatically) */ + pushResponse: (data: string) => void; + /** Signal end of stdout stream */ + close: () => void; +} diff --git a/src/test/mocks/vscode.ts b/src/test/mocks/vscode.ts new file mode 100644 index 0000000..be76c07 --- /dev/null +++ b/src/test/mocks/vscode.ts @@ -0,0 +1,48 @@ +/** + * Mock utilities for VS Code API + * Used by extension module tests that require VS Code globalState mocking + */ + +/** + * Creates a mock vscode.Memento implementation backed by an in-memory Map. + * Supports get, update, and keys operations as defined by the VS Code API. + * + * @example + * ```typescript + * const memento = createMockMemento(); + * await memento.update('key', { value: 'test' }); + * const result = memento.get('key'); + * ``` + */ +export function createMockMemento(): MockMemento { + const store = new Map(); + + return { + get: (key: string, defaultValue?: T): T | undefined => (store.get(key) as T) ?? defaultValue, + + update: async (key: string, value: unknown): Promise => { + if (value === undefined) { + store.delete(key); + } else { + store.set(key, value); + } + }, + + keys: (): readonly string[] => [...store.keys()], + + // Expose internal store for test assertions + _store: store, + }; +} + +/** + * Mock Memento interface matching vscode.Memento with test helpers + */ +export interface MockMemento { + get(key: string): T | undefined; + get(key: string, defaultValue: T): T; + update(key: string, value: unknown): Promise; + keys(): readonly string[]; + /** Internal store exposed for test assertions */ + readonly _store: Map; +} diff --git a/src/test/runPackageTest.ts b/src/test/runPackageTest.ts deleted file mode 100644 index 0e0da00..0000000 --- a/src/test/runPackageTest.ts +++ /dev/null @@ -1,86 +0,0 @@ -// @ts-check // Use JSDoc type checking - -const path = require('path'); -const fs = require('fs'); -const os = require('os'); -const { runTests } = require('@vscode/test-electron'); -const { execSync } = require('child_process'); // To run npm commands - -async function main() { - let userDataDir; // Declare here to be accessible in catch block - try { - console.log('Starting package integration test...'); - - // 1. Get package info (Use paths relative to project root where the script is invoked from) - const packageJsonPath = './package.json'; - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - // const packageName = packageJson.name; // Not used for VSIX name construction here - const packageVersion = packageJson.version; - // Construct the VSIX name to match the output of the 'package:dist' script - const vsixName = `vscode-goose-${packageVersion}.vsix`; - const vsixDistDir = './dist'; // Relative to project root - const vsixPath = path.join(vsixDistDir, vsixName); // Expected path after packaging - - console.log(`Expecting VSIX: ${vsixPath}`); - - // 2. Run the package:dist command - console.log('Packaging extension...'); - // Run from the project root directory (cwd defaults to process.cwd() which is project root) - execSync('npm run package:dist', { stdio: 'inherit' }); - console.log('Packaging complete.'); - - // 3. Check if VSIX exists - if (!fs.existsSync(vsixPath)) { - throw new Error(`Failed to find packaged VSIX at ${vsixPath}`); - } - console.log(`Found VSIX: ${vsixPath}`); - - // 4. Set up VS Code test environment options - const projectRoot = path.resolve(__dirname, '../../../'); // Go up 3 levels from out/test/test - const extensionDevelopmentPath = projectRoot; - // Point to the *compiled* suite runner using an absolute path from project root - const extensionTestsPath = path.join(projectRoot, 'out/test/test/suite/index.js'); // Absolute path - const testWorkspace = path.join(projectRoot, 'test-workspace'); // Absolute path - // Create a temporary directory for user data to ensure clean state - userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-test-user-data-')); - - // Ensure test workspace exists - if (!fs.existsSync(testWorkspace)) { - fs.mkdirSync(testWorkspace); - } - console.log(`Using temporary user data dir: ${userDataDir}`); - - - // 5. Run the tests with the VSIX installed - console.log('Launching VS Code with packaged extension installed...'); - await runTests({ - extensionDevelopmentPath, - extensionTestsPath, - // Set environment variable to signal suite runner to only run package tests - extensionTestsEnv: { 'VSCODE_PKG_TEST': '1' }, - // workspacePath: testWorkspace, // Optional - launchArgs: [ - `--install-extension=${vsixPath}`, - `--user-data-dir=${userDataDir}`, // Use clean user data dir - // '--disable-extensions' // Optionally disable other extensions - testWorkspace // Open specific workspace if needed - ], - }); - console.log('Tests finished.'); - // Clean up temporary user data dir - console.log(`Cleaning up temporary user data dir: ${userDataDir}`); - fs.rmSync(userDataDir, { recursive: true, force: true }); - - - } catch (err) { - console.error('Failed to run package integration tests:', err); - // Clean up temporary dir even on error - if (typeof userDataDir !== 'undefined' && fs.existsSync(userDataDir)) { - console.log(`Cleaning up temporary user data dir after error: ${userDataDir}`); - fs.rmSync(userDataDir, { recursive: true, force: true }); - } - process.exit(1); - } -} - -main(); diff --git a/src/test/runTest.ts b/src/test/runTest.ts deleted file mode 100644 index cad2832..0000000 --- a/src/test/runTest.ts +++ /dev/null @@ -1,41 +0,0 @@ -const path = require('path'); -const { runTests } = require('@vscode/test-electron'); - -/** - * Custom test runner that adds flags to suppress warnings - */ -async function main() { - try { - // The folder containing the Extension Manifest package.json - const extensionDevelopmentPath = path.resolve(__dirname, '../../'); - - // The path to the *compiled* test runner script in the output directory - // Should match the structure resulting from tsconfig.tests.json (rootDir: 'src', outDir: 'out/test') - const extensionTestsPath = path.resolve(__dirname, '../../out/test/test/suite/index'); - - // Additional runtime options to reduce noise - const additionalOptions = { - execArgv: [ - '--no-warnings', // Suppress Node.js warnings - '--force-node-api-uncaught-exceptions-policy=true', // Fix for N-API deprecation warnings - ] - }; - - // Download VS Code, unzip it and run the integration test - await runTests({ - extensionDevelopmentPath, - extensionTestsPath, - launchArgs: [ - '--disable-extensions', // Disable other extensions during testing - '--no-sandbox', - '--disable-gpu' - ], - ...additionalOptions - }); - } catch (err) { - console.error('Failed to run tests', err); - process.exit(1); - } -} - -main(); diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts deleted file mode 100644 index c745835..0000000 --- a/src/test/suite/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as path from 'path'; -import Mocha from 'mocha'; // Use default import for constructor -import { glob } from 'glob'; // Use named import for the function - -export function run(): Promise { - // Create the mocha test - const mocha = new Mocha({ - ui: 'tdd', // Use TDD interface (suite, test) - color: true, - timeout: 15000 // Increase timeout for potentially slower integration tests - }); - - const testsRoot = path.resolve(__dirname, '..'); // Relative to out/test/suite - - return new Promise((c, e) => { - // Determine which tests to run based on environment variable - const isPackageTest = process.env.VSCODE_PKG_TEST === '1'; - const globPattern = isPackageTest ? 'activation.test.js' : '**/**.test.js'; - const testFilesDescription = isPackageTest ? 'activation test' : 'all tests'; - - console.log(`Running ${testFilesDescription} from ${testsRoot} using pattern: ${globPattern}`); - - // Use glob to find the specified test files - glob(globPattern, { cwd: testsRoot }) - .then(files => { - if (files.length === 0) { - return e(new Error(`No test files found matching pattern '${globPattern}' in ${testsRoot}`)); - } - console.log(`Found test files: ${files.join(', ')}`); - - // Add files to the test suite - files.forEach((f: string) => mocha.addFile(path.resolve(testsRoot, f))); - - try { - // Run the mocha test - mocha.run((failures: number) => { - if (failures > 0) { - e(new Error(`${failures} tests failed.`)); - } else { - c(); - } - }); - } catch (runErr) { - console.error(runErr); - e(runErr); - } - }) - .catch(globErr => { - console.error(globErr); - return e(globErr); - }); - }); -} diff --git a/src/test/suite/utils/codeReferenceManager.test.ts b/src/test/suite/utils/codeReferenceManager.test.ts deleted file mode 100644 index dbe2dd8..0000000 --- a/src/test/suite/utils/codeReferenceManager.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import * as path from 'path'; -import { CodeReferenceManager, CodeReference } from '../../../utils/codeReferenceManager'; -import { mock, instance, when, reset, anything, verify, anyString } from 'ts-mockito'; - -// Mocks for VS Code objects -const mockEditor = mock(); -const mockDocument = mock(); -const mockSelection = mock(); - -suite('CodeReferenceManager Test Suite', () => { - let codeReferenceManager: CodeReferenceManager; - let editor: vscode.TextEditor; - let document: vscode.TextDocument; - let selection: vscode.Selection; - - // Mocking vscode.window.activeTextEditor - let originalActiveTextEditor: typeof vscode.window.activeTextEditor; - - setup(() => { - // Get instance before each test - codeReferenceManager = CodeReferenceManager.getInstance(); - - // Reset mocks - reset(mockEditor); - reset(mockDocument); - reset(mockSelection); - - // Setup instances from mocks - editor = instance(mockEditor); - document = instance(mockDocument); - selection = instance(mockSelection); - - // Default mock behaviors - when(mockEditor.document).thenReturn(document); - when(mockEditor.selection).thenReturn(selection); - when(mockDocument.uri).thenReturn(vscode.Uri.file('/fake/path/test.py')); - when(mockDocument.languageId).thenReturn('python'); - when(mockDocument.lineCount).thenReturn(10); - - // Mock vscode.window.activeTextEditor - originalActiveTextEditor = vscode.window.activeTextEditor; - Object.defineProperty(vscode.window, 'activeTextEditor', { - get: () => editor, - configurable: true // Allow redefinition in tests - }); - }); - - teardown(() => { - // Restore original activeTextEditor - Object.defineProperty(vscode.window, 'activeTextEditor', { - get: () => originalActiveTextEditor, - }); - }); - - suite('getCodeReferenceFromSelection', () => { - test('should return null if no active editor', () => { - Object.defineProperty(vscode.window, 'activeTextEditor', { get: () => undefined }); - const result = codeReferenceManager.getCodeReferenceFromSelection(); - assert.strictEqual(result, null); - }); - - test('should return null if selection is empty', () => { - when(mockSelection.isEmpty).thenReturn(true); - const result = codeReferenceManager.getCodeReferenceFromSelection(); - assert.strictEqual(result, null); - verify(mockDocument.getText(selection)).never(); // Should not attempt to get text - }); - - test('should return null if selected text is only whitespace', () => { - when(mockSelection.isEmpty).thenReturn(false); - when(mockDocument.getText(selection)).thenReturn(' \n\t '); - const result = codeReferenceManager.getCodeReferenceFromSelection(); - assert.strictEqual(result, null); - verify(mockDocument.getText(selection)).once(); - }); - - test('should return CodeReference if selection is valid', () => { - const selectedText = 'print("hello")'; - const startLine = 5; - const endLine = 5; - const startChar = 4; - const endChar = 18; - - when(mockSelection.isEmpty).thenReturn(false); - when(mockDocument.getText(selection)).thenReturn(selectedText); - when(mockSelection.start).thenReturn(new vscode.Position(startLine - 1, startChar)); // 0-based - when(mockSelection.end).thenReturn(new vscode.Position(endLine - 1, endChar)); // 0-based - - const result = codeReferenceManager.getCodeReferenceFromSelection(); - - assert.ok(result); - assert.strictEqual(result.filePath, '/fake/path/test.py'); - assert.strictEqual(result.fileName, 'test.py'); - assert.strictEqual(result.startLine, startLine); // Expect 1-based - assert.strictEqual(result.endLine, endLine); // Expect 1-based - assert.strictEqual(result.selectedText, selectedText); - assert.strictEqual(result.languageId, 'python'); - assert.ok(result.id.startsWith('test.py-5-5-')); - }); - }); - - suite('getCodeReferenceForEntireFile', () => { - test('should return null if document is null (though type hints prevent)', () => { - // Test the internal check, although TS prevents passing null directly - const result = codeReferenceManager.getCodeReferenceForEntireFile(null as any); - assert.strictEqual(result, null); - }); - - test('should return null if document content is empty', () => { - when(mockDocument.getText()).thenReturn(''); - when(mockDocument.lineCount).thenReturn(0); - const result = codeReferenceManager.getCodeReferenceForEntireFile(document); - assert.strictEqual(result, null); - verify(mockDocument.getText()).once(); - }); - - test('should return null if document content is only whitespace', () => { - when(mockDocument.getText()).thenReturn(' \n \t '); - when(mockDocument.lineCount).thenReturn(2); - const result = codeReferenceManager.getCodeReferenceForEntireFile(document); - assert.strictEqual(result, null); - verify(mockDocument.getText()).once(); - }); - - test('should return CodeReference for entire valid file', () => { - const fileContent = 'line1\nline2\nline3'; - const lineCount = 3; - when(mockDocument.getText()).thenReturn(fileContent); - when(mockDocument.lineCount).thenReturn(lineCount); - when(mockDocument.uri).thenReturn(vscode.Uri.file('/another/path/script.js')); - when(mockDocument.languageId).thenReturn('javascript'); - - const result = codeReferenceManager.getCodeReferenceForEntireFile(document); - - assert.ok(result); - assert.strictEqual(result.filePath, '/another/path/script.js'); - assert.strictEqual(result.fileName, 'script.js'); - assert.strictEqual(result.startLine, 1); - assert.strictEqual(result.endLine, lineCount); - assert.strictEqual(result.selectedText, fileContent); - assert.strictEqual(result.languageId, 'javascript'); - assert.ok(result.id.startsWith('script.js-wholefile-')); - verify(mockDocument.getText()).once(); - }); - - test('should return CodeReference for entire valid file with 1 line', () => { - const fileContent = 'line1'; - const lineCount = 1; - when(mockDocument.getText()).thenReturn(fileContent); - when(mockDocument.lineCount).thenReturn(lineCount); - when(mockDocument.uri).thenReturn(vscode.Uri.file('/single/line/file.txt')); - when(mockDocument.languageId).thenReturn('plaintext'); - - const result = codeReferenceManager.getCodeReferenceForEntireFile(document); - - assert.ok(result); - assert.strictEqual(result.filePath, '/single/line/file.txt'); - assert.strictEqual(result.fileName, 'file.txt'); - assert.strictEqual(result.startLine, 1); - assert.strictEqual(result.endLine, 1); - assert.strictEqual(result.selectedText, fileContent); - assert.strictEqual(result.languageId, 'plaintext'); - assert.ok(result.id.startsWith('file.txt-wholefile-')); - verify(mockDocument.getText()).once(); - }); - }); -}); diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts deleted file mode 100644 index 814abe0..0000000 --- a/src/test/testUtils.ts +++ /dev/null @@ -1,104 +0,0 @@ -import * as vscode from 'vscode'; -import { ExtensionContext } from 'vscode'; -import { getBinaryPath } from '../utils/binaryPath'; -import * as sinon from 'sinon'; - -export interface TestEnvironment { - context: ExtensionContext; - sandbox: sinon.SinonSandbox; - cleanup: () => void; -} - -/** - * Sets up a test environment with mock VSCode extension context - */ -export function setupTestEnvironment(): TestEnvironment { - const sandbox = sinon.createSandbox(); - - const context = { - subscriptions: [], - extensionPath: '', - extensionUri: vscode.Uri.parse('file:///test'), - environmentVariableCollection: { - persistent: false, - replace: () => { }, - append: () => { }, - prepend: () => { }, - get: () => undefined, - forEach: () => { }, - delete: () => { }, - has: () => false, - clear: () => { }, - getScoped: () => ({} as vscode.EnvironmentVariableCollection), - description: undefined, - [Symbol.iterator]: function* () { yield* []; } - }, - storageUri: vscode.Uri.parse('file:///test/storage'), - globalStorageUri: vscode.Uri.parse('file:///test/global-storage'), - logUri: vscode.Uri.parse('file:///test/logs'), - extensionMode: vscode.ExtensionMode.Test, - isNewInstall: false, - extension: { - id: 'test-extension', - extensionUri: vscode.Uri.parse('file:///test'), - isActive: true, - packageJSON: {}, - extensionPath: '', - extensionKind: vscode.ExtensionKind.UI, - exports: {}, - activate: () => Promise.resolve({}) - }, - workspaceState: { - get: () => undefined, - update: () => Promise.resolve(), - keys: () => [] - }, - globalState: { - get: () => undefined, - update: () => Promise.resolve(), - setKeysForSync: () => { }, - keys: () => [] - }, - secrets: { - get: () => Promise.resolve(undefined), - store: () => Promise.resolve(), - delete: () => Promise.resolve(), - onDidChange: new vscode.EventEmitter().event - }, - asAbsolutePath: (relativePath: string) => relativePath, - storagePath: undefined, - globalStoragePath: undefined, - logPath: undefined, - extensionRuntime: 1, - languageModelAccessInformation: { - canAccessLanguageModels: false, - onDidChangeLanguageModelAccess: new vscode.EventEmitter().event - } - } as unknown as ExtensionContext; - - const cleanup = () => { - sandbox.restore(); - }; - - return { context, sandbox, cleanup }; -} - -/** - * Creates a silent logger for testing - */ -export const silentLogger = { - info: () => { }, - error: () => { }, - debug: () => { }, - warn: () => { } -}; - -/** - * Creates a test binary path resolver - */ -export function getTestBinaryPathResolver(): (binaryName: string) => string { - return (binaryName: string) => { - // Return a fixed path for testing rather than calling the actual function - return `/test/path/to/${binaryName}`; - }; -} diff --git a/src/test/unit/apiClient.test.ts b/src/test/unit/apiClient.test.ts deleted file mode 100644 index 7c4c801..0000000 --- a/src/test/unit/apiClient.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import * as assert from 'assert'; // Use Node's built-in assert -import { ApiClient, ApiClientConfig } from '../../server/apiClient'; -import sinon from 'sinon'; -import { Logger } from '../../utils/logger'; // Import Logger class for stubbing - -// Mock the global fetch function -const mockFetch = sinon.stub(); -global.fetch = mockFetch as any; - -suite('ApiClient Tests', () => { // Changed describe to suite - let apiClient: ApiClient; - let config: ApiClientConfig; - let mockLogger: sinon.SinonStubbedInstance; // Stub the Logger class - - setup(() => { // Changed beforeEach to setup - // Reset stubs before each test - mockFetch.reset(); - mockLogger = sinon.createStubInstance(Logger); // Create a stub instance of the Logger class - mockLogger.createSource.returnsThis(); // Make createSource return the stub for chaining - - config = { - baseUrl: 'http://localhost:1234', - secretKey: 'test-secret-key', - logger: mockLogger, - debug: false, // Set to true for more verbose logging during debugging tests - }; - apiClient = new ApiClient(config); - }); - - teardown(() => { // Changed afterEach to teardown - sinon.restore(); // Restore original functions after each test - }); - - suite('setAgentPrompt', () => { // Changed describe to suite - const promptText = 'Test system prompt'; - const expectedPath = '/agent/prompt'; - const expectedMethod = 'POST'; - const expectedBody = JSON.stringify({ extension: promptText }); - // Define expectedHeaders inside the test where config is guaranteed to be set - - test('should call fetch with correct arguments and return success response', async () => { // Changed it to test - // Define expectedHeaders here, inside the test block - const expectedHeaders = { - 'Content-Type': 'application/json', - 'X-Secret-Key': config.secretKey, - }; - const mockSuccessResponse = { success: true }; - mockFetch.resolves({ - ok: true, - status: 200, - statusText: 'OK', - json: sinon.stub().resolves(mockSuccessResponse), - text: sinon.stub().resolves(JSON.stringify(mockSuccessResponse)), // Add text stub - headers: new Headers(), // Add headers stub - }); - - const result = await apiClient.setAgentPrompt(promptText); - - assert.ok(mockFetch.calledOnce, 'fetch should be called once'); - const [url, options] = mockFetch.getCall(0).args; - assert.strictEqual(url, `${config.baseUrl}${expectedPath}`, 'URL should match'); - assert.strictEqual(options.method, expectedMethod, 'Method should be POST'); - assert.deepStrictEqual(options.headers, expectedHeaders, 'Headers should match'); - assert.strictEqual(options.body, expectedBody, 'Body should match'); - assert.deepStrictEqual(result, mockSuccessResponse, 'Result should match mock response'); - assert.ok(mockLogger.info.calledWith(`Setting agent system prompt...`), 'Info log for setting prompt missing'); - assert.ok(mockLogger.info.calledWith(`Agent prompt set successfully, response: ${JSON.stringify(mockSuccessResponse)}`), 'Info log for success missing'); - }); - - test('should trim prompt with leading/trailing whitespace', async () => { - const inputPrompt = ' Trimmed Test Prompt '; - const expectedTrimmedPrompt = 'Trimmed Test Prompt'; - const expectedBody = JSON.stringify({ extension: expectedTrimmedPrompt }); - const mockSuccessResponse = { success: true }; - - mockFetch.resolves({ - ok: true, status: 200, statusText: 'OK', - json: sinon.stub().resolves(mockSuccessResponse), - text: sinon.stub().resolves(JSON.stringify(mockSuccessResponse)), - headers: new Headers(), - }); - - await apiClient.setAgentPrompt(inputPrompt); - - assert.ok(mockFetch.calledOnce, 'fetch should be called once'); - const [, options] = mockFetch.getCall(0).args; - assert.strictEqual(options.body, expectedBody, 'Body should contain trimmed prompt'); - assert.ok(mockLogger.info.calledWith(`Setting agent system prompt...`), 'Initial info log missing'); - }); - - test('should skip API call for an empty string prompt', async () => { - const inputPrompt = ''; - const result = await apiClient.setAgentPrompt(inputPrompt); - - assert.strictEqual(mockFetch.notCalled, true, 'fetch should not be called for empty prompt'); - assert.strictEqual(result, undefined, 'Should return undefined for skipped call'); - assert.ok(mockLogger.info.calledWith(`Setting agent system prompt...`), 'Initial info log missing'); - assert.ok(mockLogger.info.calledWith('Trimmed system prompt is empty. Skipping API call to /agent/prompt.'), 'Skip log missing'); - }); - - test('should skip API call for a whitespace-only prompt', async () => { - const inputPrompt = ' '; // Whitespace only - const result = await apiClient.setAgentPrompt(inputPrompt); - - assert.strictEqual(mockFetch.notCalled, true, 'fetch should not be called for whitespace-only prompt'); - assert.strictEqual(result, undefined, 'Should return undefined for skipped call'); - assert.ok(mockLogger.info.calledWith(`Setting agent system prompt...`), 'Initial info log missing'); - assert.ok(mockLogger.info.calledWith('Trimmed system prompt is empty. Skipping API call to /agent/prompt.'), 'Skip log missing for whitespace'); - }); - - test('should throw an error if the API request fails', async () => { // Changed it to test - // Define expectedHeaders here, inside the test block - const expectedHeaders = { - 'Content-Type': 'application/json', - 'X-Secret-Key': config.secretKey, - }; - const mockErrorResponse = 'Internal Server Error'; - const mockStatus = 500; - const mockStatusText = 'Server Error'; - mockFetch.resolves({ - ok: false, - status: mockStatus, - statusText: mockStatusText, - json: sinon.stub().rejects(new Error('Should not call json() on error')), // Should not be called - text: sinon.stub().resolves(mockErrorResponse), // text() is called for error body - headers: new Headers(), // Add headers stub - }); - - await assert.rejects( - async () => { - await apiClient.setAgentPrompt(promptText); - }, - (error: any) => { - assert.ok(mockFetch.calledOnce, 'fetch should be called once on error'); - const [url, options] = mockFetch.getCall(0).args; - assert.strictEqual(url, `${config.baseUrl}${expectedPath}`); - assert.strictEqual(options.method, expectedMethod); - assert.deepStrictEqual(options.headers, expectedHeaders); - assert.strictEqual(options.body, expectedBody); - assert.ok(error instanceof Error, 'Error should be an instance of Error'); - assert.ok( - error.message.includes(`API request failed: ${mockStatus} ${mockStatusText} - ${mockErrorResponse}`), - `Error message mismatch: ${error.message}` - ); - assert.ok(mockLogger.error.calledWith(`API request to ${expectedPath} failed:`), 'Error log missing'); - return true; // Indicate the error is expected - }, - 'Expected setAgentPrompt to throw an error' - ); - }); - }); - - // Add other tests for ApiClient methods here... -}); diff --git a/src/test/unit/askAboutSelection.test.ts b/src/test/unit/askAboutSelection.test.ts deleted file mode 100644 index ea3a90e..0000000 --- a/src/test/unit/askAboutSelection.test.ts +++ /dev/null @@ -1,320 +0,0 @@ -import * as assert from 'assert'; -import * as sinon from 'sinon'; -import * as vscode from 'vscode'; -import { setupTestEnvironment } from '../testUtils'; -import { CodeReferenceManager, CodeReference } from '../../utils/codeReferenceManager'; -import { MessageType } from '../../common-types'; -import { ChatProcessor } from '../../server/chat/chatProcessor'; - -suite('Enhanced Ask About Selection Feature Tests', () => { - let testEnv: ReturnType; - - // Mock dependencies and helpers - let mockEditor: any; - let mockDocument: any; - let mockSelection: any; - let mockWebviewProvider: any; - let mockChatProcessor: any; - let mockCodeReferenceManager: any; - const SELECTION_LINE_LIMIT = 100; // Same constant used in extension.ts - - setup(() => { - testEnv = setupTestEnvironment(); - - // Set up common mocks - mockDocument = { - getText: sinon.stub().returns('Mock file content'), - uri: { fsPath: '/path/to/mockFile.ts' }, - fileName: 'mockFile.ts', - languageId: 'typescript', - lineCount: 50 - }; - - mockSelection = { - isEmpty: false, - start: { line: 0 }, - end: { line: 0 } - }; - - mockEditor = { - document: mockDocument, - selection: mockSelection - }; - - // Mock VS Code window.activeTextEditor - sinon.stub(vscode.window, 'activeTextEditor').get(() => mockEditor); - - // Mock CodeReferenceManager - mockCodeReferenceManager = { - getInstance: sinon.stub().returns({ - getCodeReferenceFromSelection: sinon.stub().returns({ - id: 'mock-ref-id', - filePath: '/path/to/mockFile.ts', - fileName: 'mockFile.ts', - startLine: 1, - endLine: 1, - selectedText: 'Mock selection', - languageId: 'typescript' - }) - }) - }; - - // Mock the webview provider - mockWebviewProvider = { - postMessage: sinon.spy() - }; - - // Mock ChatProcessor for message handling - mockChatProcessor = { - sendMessage: sinon.spy() - }; - - // No need to stub global objects - we'll provide the mock directly to the handler - }); - - teardown(() => { - testEnv.cleanup(); - sinon.restore(); - }); - - /** - * Test the behavior when there is no selection (whole file case) - */ - test('should send entire file as code reference when no selection exists', () => { - // Setup test: empty selection - mockSelection.isEmpty = true; - - // Execute the askAboutSelection command handler logic - executeAskAboutSelectionHandler(mockWebviewProvider); - - // Verify: ADD_CODE_REFERENCE message sent with file content - assert.strictEqual(mockWebviewProvider.postMessage.callCount, 2); - - const firstCall = mockWebviewProvider.postMessage.getCall(0); - assert.strictEqual(firstCall.args[0].command, MessageType.ADD_CODE_REFERENCE); - - const codeRef = firstCall.args[0].codeReference; - assert.strictEqual(codeRef.selectedText, 'Mock file content'); - assert.strictEqual(codeRef.fileName, 'mockFile.ts'); - assert.strictEqual(codeRef.startLine, 1); // 1-based - assert.strictEqual(codeRef.endLine, 50); // lineCount - - // Verify focus message - const secondCall = mockWebviewProvider.postMessage.getCall(1); - assert.strictEqual(secondCall.args[0].command, MessageType.FOCUS_CHAT_INPUT); - }); - - /** - * Test the behavior when the selection is small (< 100 lines) - */ - test('should send PREPARE_MESSAGE_WITH_CODE when selection is < 100 lines', () => { - // Setup test: selection with less than 100 lines - mockSelection.isEmpty = false; - mockSelection.start.line = 5; - mockSelection.end.line = 14; // 10 lines total - mockDocument.getText = sinon.stub().returns('const smallSelection = "test";\nconsole.log(smallSelection);'); - - // Execute the askAboutSelection command handler logic - executeAskAboutSelectionHandler(mockWebviewProvider); - - // Verify: PREPARE_MESSAGE_WITH_CODE message sent - assert.strictEqual(mockWebviewProvider.postMessage.callCount, 2); - - const firstCall = mockWebviewProvider.postMessage.getCall(0); - assert.strictEqual(firstCall.args[0].command, MessageType.PREPARE_MESSAGE_WITH_CODE); - - const payload = firstCall.args[0].payload; - assert.strictEqual(payload.content, 'const smallSelection = "test";\nconsole.log(smallSelection);'); - assert.strictEqual(payload.fileName, 'mockFile.ts'); - assert.strictEqual(payload.languageId, 'typescript'); - - // Verify focus message - const secondCall = mockWebviewProvider.postMessage.getCall(1); - assert.strictEqual(secondCall.args[0].command, MessageType.FOCUS_CHAT_INPUT); - }); - - /** - * Test the behavior when the selection is large (>= 100 lines) - */ - test('should send ADD_CODE_REFERENCE when selection is >= 100 lines', () => { - // Setup test: selection with 100+ lines - mockSelection.isEmpty = false; - mockSelection.start.line = 1; - mockSelection.end.line = 100; // 100 lines total - - // Create a stub to ensure getCodeReferenceFromSelection is called - const getCodeRefStub = sinon.stub().returns({ - id: 'large-selection-id', - filePath: '/path/to/mockFile.ts', - fileName: 'mockFile.ts', - startLine: 1, - endLine: 100, - selectedText: 'Large selection content...', - languageId: 'typescript' - }); - - mockCodeReferenceManager.getInstance = sinon.stub().returns({ - getCodeReferenceFromSelection: getCodeRefStub - }); - - // Execute the askAboutSelection command handler logic - executeAskAboutSelectionHandler(mockWebviewProvider); - - // Verify: ADD_CODE_REFERENCE message is sent with the code reference - assert.strictEqual(mockWebviewProvider.postMessage.callCount, 2); - - // Check if getCodeReferenceFromSelection was called - assert.strictEqual(getCodeRefStub.called, true, "getCodeReferenceFromSelection should be called"); - - const firstCall = mockWebviewProvider.postMessage.getCall(0); - assert.strictEqual(firstCall.args[0].command, MessageType.ADD_CODE_REFERENCE); - - const codeRef = firstCall.args[0].codeReference; - assert.strictEqual(codeRef.fileName, 'mockFile.ts'); - assert.strictEqual(codeRef.startLine, 1); - assert.strictEqual(codeRef.endLine, 100); - - // Verify focus message - const secondCall = mockWebviewProvider.postMessage.getCall(1); - assert.strictEqual(secondCall.args[0].command, MessageType.FOCUS_CHAT_INPUT); - }); - - /** - * Test the ChatProcessor's handling of prependedCode - */ - test('ChatProcessor should format prependedCode in markdown when present', () => { - // Create a real ChatProcessor instance with mock dependencies - const processor = new ChatProcessor({ - getApiClient: () => ({ - streamChatResponse: sinon.stub().resolves(new Response()) - }) - } as any); - - // Store the original method to restore it after the test - const originalSendChatRequest = (processor as any).sendChatRequest; - - try { - // Replace sendChatRequest with a simple stub that does nothing - (processor as any).sendChatRequest = sinon.stub().resolves(new Response()); - - // Prepare test data - const prependedCode: CodeReference = { // Explicitly type and conform to CodeReference - id: 'prepended-test-id', - filePath: '/path/to/snippet.ts', - fileName: 'snippet.ts', - startLine: 1, - endLine: 2, // Assuming 2 lines for the snippet - selectedText: 'const code = "small snippet";\nconsole.log(code);', // Renamed from content - languageId: 'typescript' - }; - - // Call the method we're testing - processor.sendMessage( - 'Explain this code', // text - [], // codeReferencesParam - prependedCode, // prependedCode - 'msg123', // messageId - 'session123' // sessionId - ); - - // Get the messages from ChatProcessor's internal state - const internalMessages = (processor as any).currentMessages; - assert.ok(internalMessages.length > 0, 'ChatProcessor should have at least one message'); - const originalUserMessage = internalMessages[0]; - assert.strictEqual(originalUserMessage.role, 'user', 'Message should have user role'); - assert.ok(Array.isArray(originalUserMessage.content), 'Original message should have content array'); - // New design: Expecting a single TextPart with - assert.strictEqual(originalUserMessage.content.length, 1, 'Original message content should have one part'); - assert.strictEqual(originalUserMessage.content[0].type, 'text', 'Content part should be text'); - const textContent = originalUserMessage.content[0].text; - assert.ok(textContent.includes(''), 'Content should include '); - assert.ok(textContent.includes('/path/to/snippet.ts'), 'Content should include file path'); - assert.ok(textContent.includes('const code = "small snippet";'), 'Content should include code'); - assert.ok(textContent.includes('Explain this code'), 'Content should include user query'); - } finally { - // Restore the original method - if (originalSendChatRequest) { - (processor as any).sendChatRequest = originalSendChatRequest; - } - } - }); - - /** - * Helper function that simulates the core logic of the askAboutSelection command handler - */ - function executeAskAboutSelectionHandler(provider: any) { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return; - } - - const document = editor.document; - const selection = editor.selection; - const codeReferenceManager = mockCodeReferenceManager.getInstance(); - - let codeReferenceToSend: CodeReference | null = null; - let prepayloadToSend: any = null; - let actionTaken = false; - - if (selection.isEmpty) { - // No selection - use whole file - const fileContent = document.getText(); - if (!fileContent) { - return; - } - const fileName = 'mockFile.ts'; // Simplified for test - const lineCount = document.lineCount; - codeReferenceToSend = { - id: `${fileName}-1-${lineCount}-${Date.now()}`, - filePath: document.uri.fsPath, - fileName: fileName, - startLine: 1, - endLine: lineCount, - selectedText: fileContent, - languageId: document.languageId - }; - actionTaken = true; - } else { - // Selection exists - const selectedLines = selection.end.line - selection.start.line + 1; - - if (selectedLines >= SELECTION_LINE_LIMIT) { - // >= 100 lines - use code reference chip - codeReferenceToSend = codeReferenceManager.getCodeReferenceFromSelection(); - if (codeReferenceToSend) { - actionTaken = true; - } - } else { - // < 100 lines - prepare message with code - const selectedText = document.getText(selection); - prepayloadToSend = { - content: selectedText, - fileName: 'mockFile.ts', // Simplified for test - languageId: document.languageId, - }; - actionTaken = true; - } - } - - // Send the appropriate message to the webview - if (codeReferenceToSend) { - provider.postMessage({ - command: MessageType.ADD_CODE_REFERENCE, - codeReference: codeReferenceToSend - }); - } else if (prepayloadToSend) { - provider.postMessage({ - command: MessageType.PREPARE_MESSAGE_WITH_CODE, - payload: prepayloadToSend - }); - } - - // Focus the chat view and input - if (actionTaken) { - // Skipping actual vscode.commands.executeCommand for tests - provider.postMessage({ - command: MessageType.FOCUS_CHAT_INPUT - }); - } - } -}); diff --git a/src/test/unit/configReader.test.ts b/src/test/unit/configReader.test.ts deleted file mode 100644 index 63c6cb7..0000000 --- a/src/test/unit/configReader.test.ts +++ /dev/null @@ -1,240 +0,0 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import * as sinon from 'sinon'; -import { describe, it, before, after, beforeEach } from 'mocha'; -import * as path from 'path'; -import { setupTestEnvironment } from '../testUtils'; -import { readGooseConfig, FileSystem, OS } from '../../utils/configReader'; -import { logger } from '../../utils/logger'; - -describe('ConfigReader Tests', () => { - let testEnv: ReturnType; - - // Mock implementations - const mockFs: FileSystem = { - existsSync: () => true, - readFileSync: () => 'GOOSE_PROVIDER: "databricks"\nGOOSE_MODEL: "claude-3-7-sonnet"' - }; - - const mockOs: OS = { - homedir: () => '/mock/home/dir', - platform: () => 'linux' - }; - - before(() => { - testEnv = setupTestEnvironment(); - // Stub the logger methods once before all tests in this suite - testEnv.sandbox.stub(logger, 'debug'); - testEnv.sandbox.stub(logger, 'info'); - testEnv.sandbox.stub(logger, 'warn'); - testEnv.sandbox.stub(logger, 'error'); - }); - - after(() => { - testEnv.cleanup(); - }); - - beforeEach(() => { - // Reset history or perform other per-test setup if needed - // Logger stubs are already set up in the 'before' hook - }); - - it('should parse valid config file and extract provider and model', () => { - // Call the function with our mocks - const result = readGooseConfig(mockFs, mockOs); - - // Verify expected result when config is valid - assert.strictEqual(result.provider, 'databricks'); - assert.strictEqual(result.model, 'claude-3-7-sonnet'); - }); - - it('should handle missing config file', () => { - // Create mock with non-existent file - const mockFsMissing: FileSystem = { - existsSync: () => false, - readFileSync: () => { throw new Error('Should not be called'); } - }; - - // Call the function - const result = readGooseConfig(mockFsMissing, mockOs); - - // Verify null result due to missing config - assert.strictEqual(result.provider, null); - assert.strictEqual(result.model, null); - }); - - it('should handle invalid YAML content', () => { - // Create mock with invalid YAML - const mockFsInvalid: FileSystem = { - existsSync: () => true, - readFileSync: () => 'This is not valid YAML:::::' - }; - - // Call the function - const result = readGooseConfig(mockFsInvalid, mockOs); - - // Verify null result due to YAML parse error - assert.strictEqual(result.provider, null); - assert.strictEqual(result.model, null); - }); - - it('should handle file read errors', () => { - // Create mock that throws an error - const mockFsError: FileSystem = { - existsSync: () => true, - readFileSync: () => { throw new Error('Failed to read file'); } - }; - - // Call the function - const result = readGooseConfig(mockFsError, mockOs); - - // Verify null result due to file read error - assert.strictEqual(result.provider, null); - assert.strictEqual(result.model, null); - }); - - it('should handle missing GOOSE_PROVIDER key', () => { - // Create mock with missing provider - const mockFsNoProvider: FileSystem = { - existsSync: () => true, - readFileSync: () => 'GOOSE_MODEL: "claude-3-7-sonnet"' - }; - - // Call the function - const result = readGooseConfig(mockFsNoProvider, mockOs); - - // Verify partial result with missing provider - assert.strictEqual(result.provider, null); - assert.strictEqual(result.model, 'claude-3-7-sonnet'); - }); - - it('should handle missing GOOSE_MODEL key', () => { - // Create mock with missing model - const mockFsNoModel: FileSystem = { - existsSync: () => true, - readFileSync: () => 'GOOSE_PROVIDER: "databricks"' - }; - - // Call the function - const result = readGooseConfig(mockFsNoModel, mockOs); - - // Verify partial result with missing model - assert.strictEqual(result.provider, 'databricks'); - assert.strictEqual(result.model, null); - }); - - it('should handle non-string values for keys', () => { - // Create mock with invalid types - const mockFsInvalidTypes: FileSystem = { - existsSync: () => true, - readFileSync: () => 'GOOSE_PROVIDER: 123\nGOOSE_MODEL: true' - }; - - // Call the function - const result = readGooseConfig(mockFsInvalidTypes, mockOs); - - // Verify null results due to incorrect types - assert.strictEqual(result.provider, null); - assert.strictEqual(result.model, null); - }); - - it('should handle empty YAML file', () => { - // Create mock with empty file - const mockFsEmpty: FileSystem = { - existsSync: () => true, - readFileSync: () => '' - }; - - // Call the function - const result = readGooseConfig(mockFsEmpty, mockOs); - - // Verify null results due to missing config - assert.strictEqual(result.provider, null); - assert.strictEqual(result.model, null); - }); - - it('should use correct path for Windows', () => { - // Spy on the mock implementation - const readFileSpy = sinon.spy(); - - // Create Windows mock - const mockWinOs: OS = { - homedir: () => 'C:\\Users\\test', - platform: () => 'win32' - }; - - const mockWinFs: FileSystem = { - existsSync: () => true, - readFileSync: (path) => { - readFileSpy(path); - return 'GOOSE_PROVIDER: "databricks"\nGOOSE_MODEL: "claude-3-7-sonnet"'; - } - }; - - // Set APPDATA for Windows path - const originalAppData = process.env.APPDATA; - process.env.APPDATA = 'C:\\Users\\test\\AppData\\Roaming'; - - // Call the function - const result = readGooseConfig(mockWinFs, mockWinOs); - - // Check the path used is Windows format - sinon.assert.calledOnce(readFileSpy); - const pathUsed = readFileSpy.firstCall.args[0]; - - assert.ok( - pathUsed.includes('AppData') && - pathUsed.includes('Roaming') && - pathUsed.includes('Block') && - pathUsed.includes('goose') && - pathUsed.includes('config') && - pathUsed.endsWith('config.yaml') - ); - - // Restore environment - if (originalAppData) { - process.env.APPDATA = originalAppData; - } else { - delete process.env.APPDATA; - } - }); - - it('should fallback to Roaming when APPDATA is undefined', () => { - const readFileSpy = sinon.spy(); - - const mockWinOs: OS = { - homedir: () => 'C:\\Users\\test', - platform: () => 'win32' - }; - - const mockWinFs: FileSystem = { - existsSync: () => true, - readFileSync: (path) => { - readFileSpy(path); - return 'GOOSE_PROVIDER: "databricks"\nGOOSE_MODEL: "claude-3-7-sonnet"'; - } - }; - - // Ensure APPDATA is undefined - const originalAppData = process.env.APPDATA; - delete process.env.APPDATA; - - readGooseConfig(mockWinFs, mockWinOs); - - sinon.assert.calledOnce(readFileSpy); - const pathUsed = readFileSpy.firstCall.args[0]; - - assert.ok( - pathUsed.includes('AppData') && - pathUsed.includes('Roaming') && - pathUsed.includes('Block') && - pathUsed.includes('goose') && - pathUsed.includes('config') && - pathUsed.endsWith('config.yaml') - ); - - if (originalAppData) { - process.env.APPDATA = originalAppData; - } - }); -}); diff --git a/src/test/unit/server/chat/chatProcessor.test.ts b/src/test/unit/server/chat/chatProcessor.test.ts deleted file mode 100644 index c347eb2..0000000 --- a/src/test/unit/server/chat/chatProcessor.test.ts +++ /dev/null @@ -1,369 +0,0 @@ -import * as assert from 'assert'; -import * as sinon from 'sinon'; -import { ChatProcessor, ChatEvents } from '../../../../server/chat/chatProcessor'; -import { ServerManager } from '../../../../server/serverManager'; -import { SessionManager } from '../../../../server/chat/sessionManager'; -import { logger } from '../../../../utils/logger'; -import { setupTestEnvironment } from '../../../testUtils'; -import { Message } from '../../../../types'; -import { CodeReference } from '../../../../utils/codeReferenceManager'; // Corrected import path for CodeReference -import { formatMessageWithCodeContext } from '../../../../server/chat/chatProcessor'; -import { CodeContextPart } from '../../../../types'; - -suite('ChatProcessor Tests - Empty Message Validation', () => { - let chatProcessor: ChatProcessor; - let mockServerManager: sinon.SinonStubbedInstance; - let mockSessionManager: sinon.SinonStubbedInstance; - let mockApiClient: any; - let loggerInfoStub: sinon.SinonStub; - let testEnv: ReturnType; - - setup(() => { - testEnv = setupTestEnvironment(); - - loggerInfoStub = testEnv.sandbox.stub(logger, 'info'); - - mockApiClient = { - streamChatResponse: testEnv.sandbox.stub().callsFake(async () => { - const asyncGenerator = (async function* () { - yield new TextEncoder().encode(JSON.stringify({ type: 'Message', message: { role: 'assistant', content: [{ type: 'text', text: 'response part 1' }] } })); - yield new TextEncoder().encode(JSON.stringify({ type: 'Finish', reason: 'completed' })); - })(); - - const mockReadableStreamBody = { - getReader: () => { - const iterator = asyncGenerator[Symbol.asyncIterator](); - return { - async read() { - const result = await iterator.next(); - return result; // { value: Uint8Array | undefined, done: boolean } - }, - releaseLock() { }, - get closed() { return Promise.resolve(); } // Mock closed promise - }; - } - }; - - return Promise.resolve({ - ok: true, - body: mockReadableStreamBody, - status: 200, - headers: new Headers() - }); - - suite('ChatProcessor Integration - API Payload', () => { - test('POST /reply payload contains formatted and omits codeReferences', async () => { - // Arrange - const testEnv = setupTestEnvironment(); - const mockApiClient = { - streamChatResponse: sinon.stub().resolves({ - ok: true, - body: { - getReader: () => ({ - read: async () => ({ done: true }) - }) - }, - status: 200, - headers: new Headers() - }) - }; - const mockServerManager = testEnv.sandbox.createStubInstance(ServerManager); - (mockServerManager as any).getApiClient = () => mockApiClient; - (mockServerManager as any).getDefensivePrompt = () => 'Defensive prompt text'; - const mockSessionManager = testEnv.sandbox.createStubInstance(SessionManager); - mockSessionManager.getCurrentSessionId.returns('integration-session'); - mockSessionManager.getSessions.returns([]); - const chatProcessor = new ChatProcessor(mockServerManager as unknown as ServerManager); - chatProcessor.setSessionManager(mockSessionManager as unknown as SessionManager); - - // Act - const codeContext: CodeReference = { - id: 'int-1', - filePath: '/integration/test.js', - fileName: 'test.js', - startLine: 1, - endLine: 2, - selectedText: 'console.log("integration");', - languageId: 'javascript' - }; - await chatProcessor.sendMessage('Integration test message', [], codeContext); - - // Assert - sinon.assert.calledOnce(mockApiClient.streamChatResponse); - const callArgs = mockApiClient.streamChatResponse.getCall(0).args[0]; - // The payload should have a prompt array with a user message - assert.ok(Array.isArray(callArgs.prompt)); - const userMsg = callArgs.prompt.find((m: any) => m.role === 'user'); - assert.ok(userMsg, 'User message should be present in payload'); - // The content should be a single TextPart with - assert.strictEqual(userMsg.content.length, 1); - assert.strictEqual(userMsg.content[0].type, 'text'); - assert.ok(userMsg.content[0].text.includes('')); - assert.ok(userMsg.content[0].text.includes('/integration/test.js')); - assert.ok(userMsg.content[0].text.includes('Integration test message')); - // There should be no codeReferences key - assert.strictEqual('codeReferences' in userMsg, false); - }); - }); - - suite('formatMessageWithCodeContext', () => { - test('returns user query as-is when no code context', () => { - const result = formatMessageWithCodeContext('What does this code do?'); - assert.strictEqual(result, 'What does this code do?'); - }); - - test('formats message with code context (<100 lines)', () => { - const codeContext: CodeContextPart = { - id: '1', - filePath: '/foo/bar/baz.ts', - fileName: 'baz.ts', - startLine: 1, - endLine: 3, - selectedText: 'const x = 1;\nconst y = 2;\nconsole.log(x + y);', - languageId: 'typescript', - type: 'code_context' - }; - const userQuery = 'What does this code do?'; - const result = formatMessageWithCodeContext(userQuery, codeContext); - assert.ok(result.includes('')); - assert.ok(result.includes("'\/foo\/bar\/baz.ts' (see below for file content)")); - assert.ok(result.includes(' `typescript')); - assert.ok(result.includes(' const x = 1;')); - assert.ok(result.includes(' const y = 2;')); - assert.ok(result.includes(' console.log(x + y);')); - assert.ok(result.includes(' `')); - assert.ok(result.includes(userQuery)); - assert.ok(result.includes('')); - }); - - test('formats message with code context (>=100 lines)', () => { - const codeLines = Array.from({ length: 100 }, (_, i) => `line ${i + 1}`).join('\n'); - const codeContext: CodeContextPart = { - id: '2', - filePath: '/foo/largefile.py', - fileName: 'largefile.py', - startLine: 1, - endLine: 100, - selectedText: codeLines, - languageId: 'python', - type: 'code_context' - }; - const userQuery = 'Summarize this file.'; - const result = formatMessageWithCodeContext(userQuery, codeContext); - assert.ok(result.includes('')); - assert.ok(result.includes("'\/foo\/largefile.py' (see below for file content)")); - assert.ok(result.includes(' `python')); - assert.ok(result.includes("Goose, please read the file at '/foo/largefile.py'")); - assert.ok(result.includes(' `')); - assert.ok(result.includes(userQuery)); - assert.ok(result.includes('')); - }); - - test('handles various file paths and language IDs', () => { - const codeContext: CodeContextPart = { - id: '3', - filePath: 'C:\\Users\\user\\project\\main.cpp', - fileName: 'main.cpp', - startLine: 10, - endLine: 12, - selectedText: 'int main() {\n return 0;\n}', - languageId: 'cpp', - type: 'code_context' - }; - const userQuery = 'Explain the entry point.'; - const result = formatMessageWithCodeContext(userQuery, codeContext); - assert.ok(result.includes('C:\\Users\\user\\project\\main.cpp')); - assert.ok(result.includes('cpp')); - assert.ok(result.includes('int main() {')); - assert.ok(result.includes('return 0;')); - assert.ok(result.includes('}')); - assert.ok(result.includes(userQuery)); - }); - - test('handles empty selectedText as >=100 lines (instruct to read)', () => { - const codeContext: CodeContextPart = { - id: '4', - filePath: '/empty/file.js', - fileName: 'file.js', - startLine: 1, - endLine: 1, - selectedText: '', - languageId: 'javascript', - type: 'code_context' - }; - const userQuery = 'What is in this file?'; - const result = formatMessageWithCodeContext(userQuery, codeContext); - assert.ok(result.includes("Goose, please read the file at '/empty/file.js'")); - }); - }); - }), - }; - - mockServerManager = testEnv.sandbox.createStubInstance(ServerManager); - (mockServerManager as any).getApiClient = testEnv.sandbox.stub().returns(mockApiClient); - // Stub getDefensivePrompt to return a string, as it's called in sendMessage - // The actual content doesn't matter for these tests, just that it's callable. - (mockServerManager as any).getDefensivePrompt = testEnv.sandbox.stub().returns('Defensive prompt text'); - - - mockSessionManager = testEnv.sandbox.createStubInstance(SessionManager); - mockSessionManager.getCurrentSessionId.returns('test-session-id'); - // Configure getSessions to return an empty array to prevent TypeError - mockSessionManager.getSessions.returns([]); - - chatProcessor = new ChatProcessor(mockServerManager as unknown as ServerManager); - chatProcessor.setSessionManager(mockSessionManager as unknown as SessionManager); - }); - - teardown(() => { - testEnv.sandbox.restore(); - }); - - test('sendMessage should log and not proceed if text is empty and no code context', async () => { - await chatProcessor.sendMessage('', [], undefined); - sinon.assert.calledOnceWithExactly(loggerInfoStub, 'ChatProcessor: sendMessage called with empty user text and no code context. Not proceeding.'); - sinon.assert.notCalled(mockApiClient.streamChatResponse); - }); - - test('sendMessage should log and not proceed if text is whitespace and no code context', async () => { - await chatProcessor.sendMessage(' ', [], undefined); - sinon.assert.calledOnceWithExactly(loggerInfoStub, 'ChatProcessor: sendMessage called with empty user text and no code context. Not proceeding.'); - sinon.assert.notCalled(mockApiClient.streamChatResponse); - }); - - test('sendMessage should log and not proceed if text is null and no code context', async () => { - await chatProcessor.sendMessage(null as any, [], undefined); - sinon.assert.calledOnceWithExactly(loggerInfoStub, 'ChatProcessor: sendMessage called with empty user text and no code context. Not proceeding.'); - sinon.assert.notCalled(mockApiClient.streamChatResponse); - }); - - test('sendMessage should proceed if text is empty but code references are present', async () => { - const codeRefs: CodeReference[] = [{ - id: 'ref1', - filePath: 'test.ts', - fileName: 'test.ts', - startLine: 1, - endLine: 5, - selectedText: 'code', - languageId: 'typescript' - }]; - - await chatProcessor.sendMessage('', codeRefs, undefined); - - // Assert that the API call was made - sinon.assert.calledOnce(mockApiClient.streamChatResponse); - // Assert that no "not proceeding" log was generated - sinon.assert.neverCalledWith(loggerInfoStub, 'ChatProcessor: sendMessage called with empty user text and no code context. Not proceeding.'); - }); - - test('sendMessage should proceed if text is empty but prepended code is present', async () => { - const prependedCode: CodeReference = { - id: 'prepended-test-id', - filePath: '/path/to/test.ts', - fileName: 'test.ts', - startLine: 1, - endLine: 1, - selectedText: 'code', - languageId: 'typescript' - }; - - await chatProcessor.sendMessage('', [], prependedCode); - - // Assert that the API call was made - sinon.assert.calledOnce(mockApiClient.streamChatResponse); - // Assert that no "not proceeding" log was generated - sinon.assert.neverCalledWith(loggerInfoStub, 'ChatProcessor: sendMessage called with empty user text and no code context. Not proceeding.'); - }); - - test('sendMessage should proceed and call streamChatResponse if text is valid', async () => { - loggerInfoStub.resetHistory(); - - await chatProcessor.sendMessage('Hello', [], undefined); - - sinon.assert.neverCalledWith(loggerInfoStub, 'ChatProcessor: sendMessage called with empty user text and no code context. Not proceeding.'); - sinon.assert.neverCalledWith(loggerInfoStub, 'ChatProcessor: sendMessage called with empty user text (but with code context). Not proceeding as per task 2.1 focusing on user text.'); - - sinon.assert.calledOnce(mockApiClient.streamChatResponse); - }); - - test('stopGeneration should set shouldStop flag and emit FINISH event', async () => { - // Create a mock stream that yields chunks slowly - const chunks = [ - { type: 'Message', message: { role: 'assistant', content: [{ type: 'text', text: 'response part 1' }] } }, - { type: 'Message', message: { role: 'assistant', content: [{ type: 'text', text: 'response part 2' }] } } - ]; - - let chunkIndex = 0; - const mockSlowApiClient = { - streamChatResponse: testEnv.sandbox.stub().callsFake(async () => { - const mockReadableStreamBody = { - getReader: () => { - let cancelled = false; - return { - async read() { - if (cancelled || chunkIndex >= chunks.length) { - return { done: true }; - } - - // Simulate slow chunks - await new Promise(resolve => setTimeout(resolve, 50)); - - if (cancelled) { - return { done: true }; - } - - const chunk = chunks[chunkIndex++]; - return { - value: new TextEncoder().encode(JSON.stringify(chunk)), - done: false - }; - }, - cancel() { - cancelled = true; - return Promise.resolve(); - }, - releaseLock() { }, - get closed() { return Promise.resolve(); } - }; - } - }; - - return Promise.resolve({ - ok: true, - body: mockReadableStreamBody, - status: 200, - headers: new Headers() - }); - }) - }; - - const mockSlowServerManager = testEnv.sandbox.createStubInstance(ServerManager); - (mockSlowServerManager as any).getApiClient = testEnv.sandbox.stub().returns(mockSlowApiClient); - (mockSlowServerManager as any).getDefensivePrompt = testEnv.sandbox.stub().returns('Defensive prompt text'); - - const testChatProcessor = new ChatProcessor(mockSlowServerManager as unknown as ServerManager); - testChatProcessor.setSessionManager(mockSessionManager as unknown as SessionManager); - - // Set up event listener to capture FINISH event - let finishEventCalled = false; - let finishEventStatus = ''; - testChatProcessor.on(ChatEvents.FINISH, (_message, status) => { - finishEventCalled = true; - finishEventStatus = status; - }); - - // Start the message sending (this will begin streaming) - const sendPromise = testChatProcessor.sendMessage('Test message', [], undefined); - - // Wait a bit to ensure streaming has started, then stop generation - await new Promise(resolve => setTimeout(resolve, 10)); - testChatProcessor.stopGeneration(); - - // Wait for the send to complete - await sendPromise; - - // Verify that the stream was cancelled and FINISH event was emitted - assert.strictEqual(finishEventCalled, true, 'FINISH event should be emitted'); - assert.strictEqual(finishEventStatus, 'stopped', 'FINISH event should have status "stopped"'); - }); -}); diff --git a/src/test/unit/serverManager.test.ts b/src/test/unit/serverManager.test.ts deleted file mode 100644 index 88fc2b9..0000000 --- a/src/test/unit/serverManager.test.ts +++ /dev/null @@ -1,484 +0,0 @@ -import * as assert from 'assert'; -import * as sinon from 'sinon'; -import { EventEmitter } from 'events'; -import { ServerManager, ServerStatus, ServerEvents } from '../../server/serverManager'; -import * as vscode from 'vscode'; -import { ApiClient } from '../../server/apiClient'; -import * as actualGooseServer from '../../server/gooseServer'; -import * as path from 'path'; -import { ChildProcess } from 'child_process'; -import { setupTestEnvironment, silentLogger, getTestBinaryPathResolver } from '../testUtils'; -import * as configReader from '../../utils/configReader'; - -// Define the prompt text here to avoid importing from the source file in a test -const vscodePromptText = `You are an AI assistant integrated into Visual Studio Code via the Goose extension. - -The user is interacting with you through a dedicated chat panel within the VS Code editor interface. Key features include: -- A chat interface displaying the conversation history. -- Support for standard markdown formatting in your responses, rendered by VS Code. -- Support for code blocks with syntax highlighting, leveraging VS Code's capabilities. -- Tool use messages are displayed inline within the chat; detailed outputs might be presented in expandable sections or separate views depending on the tool. - -The user manages extensions primarily through VS Code's standard extension management features (Extensions viewlet) or potentially specific configuration settings within VS Code's settings UI (\`settings.json\` or a dedicated extension settings page). - -Some capabilities might be provided by built-in features of the Goose extension, while others might come from additional VS Code extensions the user has installed. Be aware of the code context potentially provided by the user (e.g., selected code snippets, open files).`; - -// Create mock ApiClient class that properly extends the actual ApiClient -class MockApiClient extends EventEmitter { - baseUrl: string; - secretKey: string; - debug: boolean; - logger: any; - events: EventEmitter; - - constructor(config: any) { - super(); - this.baseUrl = config.baseUrl; - this.secretKey = config.secretKey; - this.debug = config.debug || false; - this.logger = config.logger || { info: console.info, error: console.error }; - this.events = new EventEmitter(); - - // Add mock methods from mockApiClient - // We'll assign these in the setup function - } -} - -// Helper function to create an API client factory for tests -function createApiClientFactory(mockApiClientMethods: any) { - return function (config: any) { - const client = new MockApiClient(config); - // Add the mock methods - Object.assign(client, mockApiClientMethods); - return client; - } as any; -} - -suite('ServerManager Tests', () => { - let serverManager: ServerManager; // Use instance from beforeEach for all tests - let mockContext: Partial; - let startGoosedStub: sinon.SinonStub, Promise>; - let workspaceFoldersStub: sinon.SinonStub; - let mockApiClient: any; - let mockProcess: any; - let getBinaryPathStub: sinon.SinonStub; - let testEnv: ReturnType; - let configReaderStub: sinon.SinonStub; - let showErrorMessageStub: sinon.SinonStub; - - setup(() => { - testEnv = setupTestEnvironment(); - mockContext = testEnv.context; - - // Stub binary path resolver - getBinaryPathStub = sinon.stub(require('../../utils/binaryPath'), 'getBinaryPath'); - getBinaryPathStub.callsFake(getTestBinaryPathResolver()); - - // Stub configReader to ensure tests don't rely on actual config files - configReaderStub = testEnv.sandbox.stub(configReader, 'readGooseConfig'); - configReaderStub.returns({ - provider: 'test-provider', - model: 'test-model' - }); - - // Create mock process using Object.create and assign properties - mockProcess = Object.create(EventEmitter.prototype); - Object.assign(mockProcess, { - kill: testEnv.sandbox.stub(), - pid: 12345, - stdin: null, - stdout: Object.assign(new EventEmitter(), { pipe: testEnv.sandbox.stub() }), - stderr: Object.assign(new EventEmitter(), { pipe: testEnv.sandbox.stub() }), - stdio: [null, null, null, null, null], - unref: testEnv.sandbox.stub(), - ref: testEnv.sandbox.stub(), - connected: false, - disconnect: testEnv.sandbox.stub(), - send: testEnv.sandbox.stub(), - channel: null, - spawnargs: [], - spawnfile: '', - exitCode: null, - signalCode: null, - killed: false, - on: EventEmitter.prototype.on, - emit: EventEmitter.prototype.emit, - off: EventEmitter.prototype.off, - }); - - // Create the startGoosed stub function manually - startGoosedStub = testEnv.sandbox.stub, Promise>(); - startGoosedStub.resolves({ - port: 8000, - workingDir: path.resolve(__dirname, '../../../test-workspace'), - process: mockProcess as ChildProcess, - secretKey: 'test-secret-key' - }); - - // Mock the VSCode extension context - mockContext = { - subscriptions: [], - extensionPath: '.', - asAbsolutePath: (relativePath: string) => path.resolve(__dirname, '../../../', relativePath), - storageUri: undefined, - globalState: { - get: testEnv.sandbox.stub(), - update: testEnv.sandbox.stub(), - setKeysForSync: testEnv.sandbox.stub() - } as unknown as vscode.Memento & { setKeysForSync(keys: readonly string[]): void }, - workspaceState: {} as vscode.Memento, - secrets: {} as vscode.SecretStorage, - extensionMode: vscode.ExtensionMode.Development, - globalStorageUri: {} as vscode.Uri, - logUri: {} as vscode.Uri, - logPath: './logs' - }; - - // Mock the workspace folders - const mockWorkspaceFolder = { - uri: vscode.Uri.file(path.resolve(__dirname, '../../../test-workspace')), - name: 'Test Workspace', - index: 0 - }; - workspaceFoldersStub = testEnv.sandbox.stub(vscode.workspace, 'workspaceFolders'); - workspaceFoldersStub.value([mockWorkspaceFolder]); - - // Mock the ApiClient constructor - mockApiClient = { - getAgentVersions: testEnv.sandbox.stub().resolves({ versions: ['1.0.0', '2.0.0'] }), - createAgent: testEnv.sandbox.stub().resolves({ id: 'test-agent-id' }), - request: testEnv.sandbox.stub().callsFake(async (_path: string, _options: any) => { // Mark params as unused - // Simulate a basic successful response for tests that might call the generic request - return Promise.resolve({ ok: true, status: 200, json: async () => ({}), text: async () => '' }); - }), - getConversations: testEnv.sandbox.stub().resolves([]), - createConversation: testEnv.sandbox.stub().resolves({ id: 'test-conversation-id' }), - sendMessage: testEnv.sandbox.stub().resolves({ id: 'test-message-id' }), - getConfiguration: testEnv.sandbox.stub().resolves({}), - updateConfiguration: testEnv.sandbox.stub().resolves({}), - checkStatus: testEnv.sandbox.stub().resolves(true), - addExtension: testEnv.sandbox.stub().resolves({ id: 'test-extension-id' }), - setAgentPrompt: testEnv.sandbox.stub().resolves({ success: true }), // Add mock for setAgentPrompt - streamMessage: testEnv.sandbox.stub().callsFake(() => { - const emitter = new EventEmitter(); - setTimeout(() => { - emitter.emit('data', { content: 'test content' }); - emitter.emit('end'); - }, 10); - return emitter; - }), - getProviders: testEnv.sandbox.stub().resolves([{ id: 'test-provider', name: 'Test Provider' }]), // Add stub - listSessions: testEnv.sandbox.stub().resolves([{ id: 'test-session-1', name: 'Test Session 1' }]), // Add stub - getSessionHistory: testEnv.sandbox.stub().resolves({ messages: [] }), // Add stub - renameSession: testEnv.sandbox.stub().resolves(true), // Add stub - deleteSession: testEnv.sandbox.stub().resolves(true), // Add stub - streamChatResponse: testEnv.sandbox.stub().callsFake(() => { // Add stub for streaming - const emitter = new EventEmitter(); - process.nextTick(() => { - emitter.emit('data', { type: 'text', content: 'Mock response chunk' }); - emitter.emit('end'); - }); - // Return something Response-like for the stubbed streamChatResponse - async function* generator() { - yield new TextEncoder().encode(JSON.stringify({ type: 'text', content: 'Mock response chunk' })); - } - return Promise.resolve({ ok: true, body: generator(), status: 200 } as any); - }), - setSecretProviderKeys: testEnv.sandbox.stub(), // Add stub for the new method - - // Example of mocking a method that emits an event - }; - - // Create the server manager with dependencies - serverManager = new ServerManager(mockContext as vscode.ExtensionContext, { - startGoosed: startGoosedStub, - getBinaryPath: (_context, binaryName) => `/test/path/to/${binaryName}`, - ApiClient: createApiClientFactory(mockApiClient) - }); - (serverManager as any).logger = silentLogger; - - // Stub vscode.window.showErrorMessage - showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage'); - - // Add a dummy error listener to prevent potential issues with emit in tests - serverManager.on(ServerEvents.ERROR, () => { /* No-op listener */ }); - }); - - teardown(() => { - getBinaryPathStub.restore(); - testEnv.cleanup(); - if (showErrorMessageStub) { showErrorMessageStub.restore(); } - }); - - test('should have stopped status initially', () => { - assert.strictEqual(serverManager.getStatus(), ServerStatus.STOPPED); - }); - - test('should emit status change events', async () => { - const statusChangeListener = sinon.spy(); - serverManager.on(ServerEvents.STATUS_CHANGE, statusChangeListener); - await serverManager.start(); - // Check intermediate status if needed, e.g., STARTING - // sinon.assert.calledWith(statusChangeListener, ServerStatus.STARTING); - assert.strictEqual(serverManager.getStatus(), ServerStatus.RUNNING); - sinon.assert.calledWith(statusChangeListener, ServerStatus.RUNNING); - }); - - test('should configure agent correctly on successful start', async () => { - // Stub the config reader *before* starting - configReaderStub.returns({ provider: 'test-provider', model: 'test-model' }); - - // --- Act --- - await serverManager.start(); - - // --- Assert --- - assert.strictEqual(serverManager.getStatus(), ServerStatus.RUNNING); - - // Verify startGoosed was called - sinon.assert.calledOnce(startGoosedStub); - - // Verify createAgent was called (getProviders and setSecretProviderKeys are removed) - sinon.assert.calledOnce(mockApiClient.createAgent); - // Optionally, verify it was called with the correct arguments from the stubbed config - sinon.assert.calledWith(mockApiClient.createAgent, 'test-provider', 'test-model'); - }); - - test('should initialize ApiClient correctly after started', async () => { - await serverManager.start(); - const apiClient = serverManager.getApiClient(); - assert.ok(apiClient, 'ApiClient should be initialized'); - // Verify the ApiClient was created with the correct configuration by checking it was called with the expected port - sinon.assert.calledOnce(startGoosedStub); - // The MockApiClient has a public baseUrl property we can check - assert.strictEqual((apiClient as any).baseUrl, 'http://127.0.0.1:8000'); - }); - - test('should handle errors during server start', async () => { - // Ensure startGoosed fails for this specific test - const error = new Error('Failed to start'); - startGoosedStub.rejects(error); - - const errorListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.ERROR, errorListener); - - const result = await serverManager.start(); - - // Assertions: - assert.strictEqual(result, false, 'start() should return false when configureAgent throws'); - assert.strictEqual(serverManager.getStatus(), ServerStatus.ERROR, 'Status should be ERROR'); - sinon.assert.calledOnce(errorListener); - }); - - test('should log error and set status to ERROR if configureAgent fails', async () => { - // Define mock process locally for this test scope - const mockServerProcess = { - on: sinon.stub(), - kill: sinon.stub(), - pid: 123 - } as unknown as ChildProcess; - - // Stub startGoosed to succeed - const startGoosedStub = sinon.stub().resolves({ - port: 12345, - process: mockServerProcess - }); - - // Stub createAgent to fail on the main mockApiClient (from beforeEach) - const failureStub = sinon.stub().rejects(new Error('Agent creation failed')); - mockApiClient.createAgent = failureStub; - - // Spy on the logger's error method directly on the instance - const logErrorSpy = sinon.spy((serverManager as any).logger, 'error'); - - let caughtError: Error | null = null; - // Start server - configureAgent should fail internally but start() should catch - let startResult: boolean | undefined; - try { - startResult = await serverManager.start(); - } catch (err: any) { - caughtError = err; - console.error("*** TEST CAUGHT ERROR ***", err); // Log if test catches it - } - - // Assertions: - assert.strictEqual(startResult, false, 'start() should return false'); - assert.strictEqual(serverManager.getStatus(), ServerStatus.ERROR, 'Status should be ERROR'); - sinon.assert.calledOnce(failureStub); // Ensure the stubbed failure was actually called - assert.strictEqual(caughtError, null, 'Error should have been caught by serverManager.start(), not the test'); - - // Check that the start() method's catch block logged the error that bubbled up - sinon.assert.calledWithMatch(logErrorSpy, 'Error starting Goose server:', sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Agent creation failed'))); - logErrorSpy.restore(); // Clean up spy - }); - - test('should handle server process exit', async () => { - // For the process tests, we need to manually set the serverInfo property - await serverManager.start(); - - // Verify we have a serverInfo object - const serverInfo = (serverManager as any).serverInfo; - assert.ok(serverInfo, 'serverInfo should exist after starting'); - - const exitListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.SERVER_EXIT, exitListener); - - const statusChangeListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.STATUS_CHANGE, statusChangeListener); - - // Trigger the close event on the mock process - serverInfo.process.emit('close', 0); - - sinon.assert.calledWith(exitListener, 0); - assert.strictEqual(serverManager.getStatus(), ServerStatus.STOPPED); - }); - - test('should handle server process crash', async () => { - await serverManager.start(); - - // Verify we have a serverInfo object - const serverInfo = (serverManager as any).serverInfo; - assert.ok(serverInfo, 'serverInfo should exist after starting'); - - const exitListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.SERVER_EXIT, exitListener); - - const statusChangeListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.STATUS_CHANGE, statusChangeListener); - - // Trigger the close event with an error code - serverInfo.process.emit('close', 1); - - sinon.assert.calledWith(exitListener, 1); - assert.strictEqual(serverManager.getStatus(), ServerStatus.ERROR); - }); - - test('should handle server process exit with null code', async () => { - await serverManager.start(); - - // Verify we have a serverInfo object - const serverInfo = (serverManager as any).serverInfo; - assert.ok(serverInfo, 'serverInfo should exist after starting'); - - const exitListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.SERVER_EXIT, exitListener); - - const statusChangeListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.STATUS_CHANGE, statusChangeListener); - - // Trigger the close event with null code - serverInfo.process.emit('close', null); - - sinon.assert.calledWith(exitListener, null); - assert.strictEqual(serverManager.getStatus(), ServerStatus.ERROR); - }); - - test('should not emit server exit event when stopping server manually', async () => { - await serverManager.start(); - - const exitListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.SERVER_EXIT, exitListener); - - await serverManager.stop(); - - // No exit event should be emitted, only status changes - sinon.assert.notCalled(exitListener); - assert.strictEqual(serverManager.getStatus(), ServerStatus.STOPPED); - }); - - // Tests for configReader integration - test('should load provider and model from config', async () => { - // The global stub is already set up in the setup function - await serverManager.start(); - - // Verify that the provider and model from config are passed to the API client - sinon.assert.calledWith(mockApiClient.createAgent, 'test-provider', 'test-model'); - }); - - test('should fail to start when GOOSE_PROVIDER is missing', async () => { - // Update the global stub for this test - configReaderStub.restore(); // Remove the global stub first - configReaderStub = testEnv.sandbox.stub(configReader, 'readGooseConfig'); - configReaderStub.returns({ - provider: null, - model: 'test-model' - }); - - const errorListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.ERROR, errorListener); - - const result = await serverManager.start(); - - // Verify the server fails to start - assert.strictEqual(result, false, 'Server should fail to start with missing provider'); - assert.strictEqual(serverManager.getStatus(), ServerStatus.ERROR); - - // Verify error is shown to user - sinon.assert.calledOnce(showErrorMessageStub); - sinon.assert.calledOnce(errorListener); - - // Verify the error message mentions the missing key - sinon.assert.calledWith(showErrorMessageStub, - sinon.match.string.and(sinon.match('GOOSE_PROVIDER'))); - }); - - test('should fail to start when GOOSE_MODEL is missing', async () => { - // Update the global stub for this test - configReaderStub.restore(); // Remove the global stub first - configReaderStub = testEnv.sandbox.stub(configReader, 'readGooseConfig'); - configReaderStub.returns({ - provider: 'test-provider', - model: null - }); - - const errorListener = testEnv.sandbox.spy(); - serverManager.on(ServerEvents.ERROR, errorListener); - - const result = await serverManager.start(); - - // Verify the server fails to start - assert.strictEqual(result, false, 'Server should fail to start with missing model'); - assert.strictEqual(serverManager.getStatus(), ServerStatus.ERROR); - - // Verify error is shown to user - sinon.assert.calledOnce(showErrorMessageStub); - sinon.assert.calledOnce(errorListener); - - // Verify the error message mentions the missing key - sinon.assert.calledWith(showErrorMessageStub, - sinon.match.string.and(sinon.match('GOOSE_MODEL'))); - }); - - test('should re-read config file after stop and restart', async () => { - // Update the global stub for this test - configReaderStub.restore(); // Remove the global stub first - configReaderStub = testEnv.sandbox.stub(configReader, 'readGooseConfig'); - configReaderStub.onFirstCall().returns({ - provider: 'first-provider', - model: 'first-model' - }); - - await serverManager.start(); - - // Verify first provider/model used - sinon.assert.calledWith(mockApiClient.createAgent, 'first-provider', 'first-model'); - - // Reset call history for next verification - mockApiClient.createAgent.resetHistory(); - startGoosedStub.resetHistory(); // Also reset the startGoosed stub if checking its calls - - // Stop the first server instance - await serverManager.stop(); - - // Change config stub for the *next* read - configReaderStub.onSecondCall().returns({ - provider: 'second-provider', - model: 'second-model' - }); - - // Start the second server - should read the new config - await serverManager.start(); - - // Verify second provider/model used - sinon.assert.calledWith(mockApiClient.createAgent, 'second-provider', 'second-model'); - }); -}); diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index e4f2397..0000000 --- a/src/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './messages'; diff --git a/src/types/messages.ts b/src/types/messages.ts deleted file mode 100644 index 7e12997..0000000 --- a/src/types/messages.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Message types that match the Rust message structures - * for direct serialization between client and server - */ - -import { CodeReference } from '../utils/codeReferenceManager'; - -export type Role = 'user' | 'assistant'; - -/** - * Represents a plain text segment, potentially containing user-typed markdown. - */ -export interface TextPart { - type: 'text'; - text: string; - annotations?: Record; -} - -/** - * Represents a snippet of code context, directly using or extending CodeReference. - * This part is intended for structured data transfer and specific UI rendering. - */ -export interface CodeContextPart extends CodeReference { - type: 'code_context'; - // id, filePath, fileName, startLine, endLine, selectedText, languageId are inherited from CodeReference. -} - -export interface ImageContent { - type: 'image'; - data: string; - mimeType: string; - annotations?: Record; -} - -export type Content = TextPart | CodeContextPart | ImageContent; // Updated to include CodeContextPart - -export interface ToolCall { - name: string; - arguments: Record; -} - -export interface ToolCallResult { - status: 'success' | 'error'; - value?: T; - error?: string; -} - -export interface ToolRequest { - id: string; - toolCall: ToolCallResult; -} - -export interface ToolResponse { - id: string; - toolResult: ToolCallResult; // Changed Content[] to MessageContent[] -} - -export interface ToolConfirmationRequest { - id: string; - toolName: string; - arguments: Record; - prompt?: string; -} - -export interface ToolRequestMessageContent { - type: 'toolRequest'; - id: string; - toolCall: ToolCallResult; -} - -export interface ToolResponseMessageContent { - type: 'toolResponse'; - id: string; - toolResult: ToolCallResult; // Changed Content[] to MessageContent[] -} - -export interface ToolConfirmationRequestMessageContent { - type: 'toolConfirmationRequest'; - id: string; - toolName: string; - arguments: Record; - prompt?: string; -} - -export type MessageContent = - | TextPart // Changed from TextContent - | CodeContextPart // Added - | ImageContent - | ToolRequestMessageContent - | ToolResponseMessageContent - | ToolConfirmationRequestMessageContent; - -export interface Message { - id?: string; - role: Role; - created: number; - content: MessageContent[]; -} - -// Helper functions to create messages -export function createUserMessage(text: string): Message { - return { - id: generateId(), - role: 'user', - created: Math.floor(Date.now() / 1000), - content: [{ type: 'text', text } as TextPart], // Cast to TextPart - }; -} - -export function createAssistantMessage(text: string): Message { - return { - id: generateId(), - role: 'assistant', - created: Math.floor(Date.now() / 1000), - content: [{ type: 'text', text } as TextPart], // Cast to TextPart - }; -} - -export function createToolRequestMessage( - id: string, - toolName: string, - args: Record -): Message { - return { - id: generateId(), - role: 'assistant', - created: Math.floor(Date.now() / 1000), - content: [ - { - type: 'toolRequest', - id, - toolCall: { - status: 'success', - value: { - name: toolName, - arguments: args, - }, - }, - }, - ], - }; -} - -export function createToolResponseMessage(id: string, result: MessageContent[]): Message { // Changed Content[] to MessageContent[] - return { - id: generateId(), - role: 'user', - created: Math.floor(Date.now() / 1000), - content: [ - { - type: 'toolResponse', - id, - toolResult: { - status: 'success', - value: result, - }, - }, - ], - }; -} - -export function createToolErrorResponseMessage(id: string, error: string): Message { - return { - id: generateId(), - role: 'user', - created: Math.floor(Date.now() / 1000), - content: [ - { - type: 'toolResponse', - id, - toolResult: { - status: 'error', - error, - }, - }, - ], - }; -} - -// Generate a unique ID for messages -function generateId(): string { - return Math.random().toString(36).substring(2, 10); -} - -// Helper functions to extract content from messages -export function getTextContent(message: Message): string { - return message.content - .filter((content): content is TextPart => content.type === 'text') // Changed TextContent to TextPart - .map((content) => content.text) - .join('\n'); -} - -export function getToolRequests(message: Message): ToolRequestMessageContent[] { - return message.content.filter( - (content): content is ToolRequestMessageContent => content.type === 'toolRequest' - ); -} - -export function getToolResponses(message: Message): ToolResponseMessageContent[] { - return message.content.filter( - (content): content is ToolResponseMessageContent => content.type === 'toolResponse' - ); -} - -export function getToolConfirmationContent( - message: Message -): ToolConfirmationRequestMessageContent | undefined { - return message.content.find( - (content): content is ToolConfirmationRequestMessageContent => - content.type === 'toolConfirmationRequest' - ); -} - -export function hasCompletedToolCalls(message: Message): boolean { - const toolRequests = getToolRequests(message); - if (toolRequests.length === 0) {return false;} - - // For now, we'll assume all tool calls are completed when this is checked - // In a real implementation, you'd need to check if all tool requests have responses - // by looking through subsequent messages - return true; -} diff --git a/src/utils/binaryPath.ts b/src/utils/binaryPath.ts deleted file mode 100644 index 37c9ddc..0000000 --- a/src/utils/binaryPath.ts +++ /dev/null @@ -1,161 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as vscode from 'vscode'; -import { ExtensionContext } from 'vscode'; -import { logger as singletonLogger } from './logger'; - -const logger = singletonLogger.createSource('BinaryPath'); - -/** - * Get the path to the Goose binary by looking for the installed Goose Desktop application. - * @param _context The VSCode extension context (currently unused but kept for potential future use) - * @param binaryName The name of the binary to find (e.g., 'goosed') - * @returns The absolute path to the binary - * @throws Error if the binary cannot be found, with a user-friendly message on how to install Goose Desktop - */ -export function getBinaryPath(_context: ExtensionContext, binaryName: string): string { - const platform = process.platform; - const homeDir = os.homedir(); - const isDev = process.env.NODE_ENV === 'development'; // Keep dev check for potential overrides - - // On Windows, use .exe suffix - const executableName = platform === 'win32' ? `${binaryName}.exe` : binaryName; - - // Define potential base paths for the Goose Desktop installation - const basePaths: string[] = []; - - // check for a user-defined binary path and push to the top of the directory list if found - const userBinaryPath = vscode.workspace.getConfiguration('goose.server').get('path'); - if (userBinaryPath && userBinaryPath.trim().length > 0) { - const normalizedPath = path.normalize(userBinaryPath); - logger.info(`User-defined binary path found: ${normalizedPath}`); - basePaths.push(normalizedPath); - } - - if (platform === 'darwin') { // macOS - // Check multiple potential paths for macOS - Electron apps can have different structures - const macAppPaths = [ - '/Applications/Goose.app', - path.join(homeDir, 'Applications/Goose.app') - ]; - - // For each base app path, check multiple possible locations where the binary could be - for (const appPath of macAppPaths) { - basePaths.push( - // Standard Resources/app/bin structure - path.join(appPath, 'Contents/Resources/app/bin'), - // Direct Resources/bin structure - path.join(appPath, 'Contents/Resources/bin'), - // MacOS directory - path.join(appPath, 'Contents/MacOS'), - // Just Resources directory - path.join(appPath, 'Contents/Resources'), - // app.asar structure - path.join(appPath, 'Contents/Resources/app.asar.unpacked/bin'), - // Try Node modules bin - sometimes used for prebuilts in Electron apps - path.join(appPath, 'Contents/Resources/app/node_modules/.bin') - ); - } - } else if (platform === 'win32') { // Windows - const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'); - const programFiles = process.env['ProgramFiles'] || 'C:\\Program Files'; - - // Windows Electron app paths - basePaths.push( - path.join(localAppData, 'Programs', 'Goose', 'resources', 'app', 'bin'), // Default install location - path.join(localAppData, 'Programs', 'Goose', 'resources', 'bin'), - path.join(localAppData, 'Programs', 'Goose', 'resources', 'app.asar.unpacked', 'bin'), - path.join(programFiles, 'Goose', 'resources', 'app', 'bin'), // Alternative install location - path.join(programFiles, 'Goose', 'resources', 'bin'), - path.join(programFiles, 'Goose', 'resources', 'app.asar.unpacked', 'bin') - ); - } else { // Linux (assuming common locations) - basePaths.push( - '/opt/Goose/resources/app/bin', // Common install location - '/opt/Goose/resources/bin', - '/opt/Goose/resources/app.asar.unpacked/bin', - '/usr/lib/goose/resources/bin', // .deb install location - '/usr/local/Goose/resources/app/bin', - '/usr/local/Goose/resources/bin', - '/usr/local/bin', // Global bin where the goosed might be symlinked - path.join(homeDir, '.local/share/Goose/resources/app/bin'), // User-specific install - path.join(homeDir, '.local/share/Goose/resources/bin'), - path.join(homeDir, '.local/share/Goose/resources/app.asar.unpacked/bin') - ); - } - - // Construct full possible paths - const possiblePaths = basePaths.map(base => path.join(base, executableName)); - - // --- Development Override --- - // If in development, also check the target/release directory relative to the extension - // This allows testing with a locally built binary without needing the full desktop app installed. - if (isDev && _context) { // Check _context exists before using it - // Add paths relative to the extension for development builds - possiblePaths.unshift( // Add dev paths to the beginning of the search list - path.join(_context.extensionPath, '..', '..', 'target', 'release', executableName), - path.join(_context.extensionPath, '..', '..', '..', 'target', 'release', executableName) - ); - } - // --- End Development Override --- - - logger.info('Checking for binary:', executableName, 'in paths:', possiblePaths); - - // Try each path and return the first one that exists - for (const binPath of possiblePaths) { - try { - if (fs.existsSync(binPath)) { - logger.info(`Found potential binary at: ${binPath}`); - // Ensure the file is executable (especially on Unix-like systems) - if (platform !== 'win32') { - try { - fs.accessSync(binPath, fs.constants.X_OK); - } catch (execError) { - logger.warn(`Binary found at ${binPath} but is not executable. Attempting to chmod.`); - try { - fs.chmodSync(binPath, 0o755); // Set executable permissions - logger.info(`Successfully set executable permission for ${binPath}`); - } catch (chmodError) { - logger.error(`Failed to set executable permission for ${binPath}:`, chmodError); - // Continue checking other paths, maybe another one works - continue; - } - } - } - logger.info(`Binary confirmed executable: ${binPath}`); - return binPath; - } - } catch (error) { - // Log errors during checks but continue trying other paths - logger.error(`Error checking path ${binPath}:`, error); - } - } - - // If we get here, we couldn't find the binary - let errorMessage = `Could not find the ${binaryName} executable. Please ensure Goose Desktop is installed.`; - - // Create a more helpful platform-specific message - let installMessage = ''; - if (platform === 'darwin') { - installMessage = 'Please download and install Goose Desktop from https://block.github.io/goose/ and ensure it is installed in /Applications or ~/Applications.'; - } else if (platform === 'win32') { - installMessage = 'Please download and install Goose Desktop from https://block.github.io/goose/ and ensure it completes installation to your Program Files or Local AppData folder.'; - } else { - installMessage = 'Please download and install Goose Desktop from https://block.github.io/goose/ following the Linux installation instructions.'; - } - - // Show an error message to the user with instructions - vscode.window.showErrorMessage( - `${errorMessage} ${installMessage}`, - { modal: true }, - { title: 'Download Goose', id: 'download' } - ).then(selection => { - if (selection && selection.id === 'download') { - vscode.env.openExternal(vscode.Uri.parse('https://block.github.io/goose/')); - } - }); - - logger.error(`Could not find binary. Checked paths: ${possiblePaths.join(', ')}`); - throw new Error(`${errorMessage} ${installMessage}`); -} diff --git a/src/utils/codeActionProvider.ts b/src/utils/codeActionProvider.ts deleted file mode 100644 index 621b498..0000000 --- a/src/utils/codeActionProvider.ts +++ /dev/null @@ -1,103 +0,0 @@ -import * as vscode from 'vscode'; - -/** - * Provides code actions for diagnostics and selected code - */ -export class GooseCodeActionProvider implements vscode.CodeActionProvider { - /** - * Provide code actions for the given document and range. - */ - public provideCodeActions( - _document: vscode.TextDocument, - range: vscode.Range | vscode.Selection, - context: vscode.CodeActionContext, - _token: vscode.CancellationToken - ): vscode.ProviderResult<(vscode.Command | vscode.CodeAction)[]> { - const actions: vscode.CodeAction[] = []; - - // Create actions based on diagnostics - if (context.diagnostics.length > 0) { - // Group actions by diagnostic to avoid duplicates - const diagnosticsMap = new Map(); - - // Only include one action per unique diagnostic message - for (const diagnostic of context.diagnostics) { - const key = `${diagnostic.message}:${diagnostic.range.start.line}`; - if (!diagnosticsMap.has(key)) { - diagnosticsMap.set(key, diagnostic); - } - } - - // Create an action for each unique diagnostic - for (const [_, diagnostic] of diagnosticsMap) { - const action = this.createDiagnosticAction(diagnostic); - if (action) { - actions.push(action); - } - } - } - - // Add only the "Ask Goose about this code" action if there's a selection - if (!range.isEmpty) { - actions.push(this.createGeneralAction( - 'Ask Goose about this code', - 'goose.askAboutSelection', - vscode.CodeActionKind.QuickFix - )); - } - - return actions; - } - - /** - * Create an action for a diagnostic - */ - private createDiagnosticAction( - diagnostic: vscode.Diagnostic - ): vscode.CodeAction | null { - const action = new vscode.CodeAction( - `Ask Goose about this code: ${this.trimDiagnosticMessage(diagnostic.message)}`, - vscode.CodeActionKind.QuickFix - ); - - action.command = { - title: 'Ask Goose about this code', - command: 'goose.askAboutSelection' - }; - - action.diagnostics = [diagnostic]; - action.isPreferred = true; - - return action; - } - - /** - * Create a general action for selected code - */ - private createGeneralAction( - title: string, - command: string, - kind: vscode.CodeActionKind - ): vscode.CodeAction { - const action = new vscode.CodeAction(title, kind); - - action.command = { - title, - command - }; - - return action; - } - - /** - * Trim a diagnostic message to a reasonable length for display - */ - private trimDiagnosticMessage(message: string): string { - const maxLength = 50; - if (message.length <= maxLength) { - return message; - } - - return message.substring(0, maxLength - 3) + '...'; - } -} diff --git a/src/utils/codeReferenceManager.ts b/src/utils/codeReferenceManager.ts deleted file mode 100644 index 4a41028..0000000 --- a/src/utils/codeReferenceManager.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as vscode from 'vscode'; -import * as path from 'path'; - -export interface CodeReference { - id: string; - filePath: string; - fileName: string; - startLine: number; - endLine: number; - selectedText: string; - languageId: string; -} - -export class CodeReferenceManager { - private static instance: CodeReferenceManager; - - private constructor() { } - - public static getInstance(): CodeReferenceManager { - if (!CodeReferenceManager.instance) { - CodeReferenceManager.instance = new CodeReferenceManager(); - } - return CodeReferenceManager.instance; - } - - /** - * Gets a code reference from the active editor's selection - */ - public getCodeReferenceFromSelection(): CodeReference | null { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return null; - } - - const document = editor.document; - const selection = editor.selection; - - if (selection.isEmpty) { - return null; - } - - const selectedText = document.getText(selection); - // If the selected text is only whitespace, treat it as empty - if (selectedText.trim() === '') { - return null; - } - - const filePath = document.uri.fsPath; - const fileName = path.basename(filePath); - const startLine = selection.start.line + 1; // 1-based line numbers - const endLine = selection.end.line + 1; // 1-based line numbers - const languageId = document.languageId; - - return { - id: `${fileName}-${startLine}-${endLine}-${Date.now()}`, - filePath, - fileName, - startLine, - endLine, - selectedText, - languageId - }; - } - - /** - * Gets a code reference for the entire content of a document. - */ - public getCodeReferenceForEntireFile(document: vscode.TextDocument): CodeReference | null { - if (!document) { - return null; - } - - const fileContent = document.getText(); - if (fileContent.trim() === '') { - return null; // Do not create reference for empty or whitespace-only file - } - - const filePath = document.uri.fsPath; - const fileName = path.basename(filePath); - const startLine = 1; // For the whole file, starts at line 1 - const endLine = document.lineCount > 0 ? document.lineCount : 1; // Ends at the last line - const languageId = document.languageId; - - return { - id: `${fileName}-wholefile-${Date.now()}`, - filePath, - fileName, - startLine, - endLine, - selectedText: fileContent, // The entire file content - languageId - }; - } - - /** - * Formats a code reference for display in the chat - */ - public formatCodeReferenceForChat(codeRef: CodeReference): string { - return `From ${codeRef.filePath}:${codeRef.startLine}-${codeRef.endLine}:\n\`\`\`${codeRef.languageId}\n${codeRef.selectedText}\n\`\`\``; - } - - /** - * Gets a short display string for the code reference chip - */ - public getCodeReferenceDisplayString(codeRef: CodeReference): string { - return `${codeRef.fileName}:${codeRef.startLine}-${codeRef.endLine}`; - } -} diff --git a/src/utils/configReader.ts b/src/utils/configReader.ts deleted file mode 100644 index 410ce11..0000000 --- a/src/utils/configReader.ts +++ /dev/null @@ -1,123 +0,0 @@ -import * as fsDefault from 'fs'; -import * as osDefault from 'os'; -import * as path from 'path'; -import * as YAML from 'yaml'; -import { logger as singletonLogger } from './logger'; - -const logger = singletonLogger.createSource('ConfigReader'); - -export interface GooseConfig { - provider: string | null; - model: string | null; -} - -/** - * File system interface for testing and dependency injection - */ -export interface FileSystem { - existsSync(path: string): boolean; - readFileSync(path: string, options?: { encoding?: string, flag?: string } | string): string | Buffer; -} - -/** - * OS interface for testing and dependency injection - */ -export interface OS { - homedir(): string; - platform(): string; -} - -/** - * Get the path to the Goose config file based on the current operating system - * @param os The OS module to use (for testing) - * @returns The path to the config file, or null if the platform isn't supported or APPDATA is missing on Windows. - */ -export function getConfigPath(os: OS = osDefault): string | null { - const homeDir = os.homedir(); - let configPath: string; - - switch (os.platform()) { - case 'win32': - // Windows path: %APPDATA%\Block\goose\config\config.yaml - // Fallback to the typical Roaming path if APPDATA is undefined - const appData = process.env.APPDATA || - path.join(homeDir, 'AppData', 'Roaming'); - configPath = path.join(appData, 'Block', 'goose', 'config', 'config.yaml'); - break; - case 'darwin': // macOS - case 'linux': - default: // Assume Unix-like structure for other platforms - configPath = path.join(homeDir, '.config', 'goose', 'config.yaml'); - break; - } - return configPath; -} - -/** - * Read and parse the Goose configuration file - * @param fs Optional file system implementation for testing - * @param os Optional OS implementation for testing - * @returns A GooseConfig object containing provider and model, or null values if not found or on error. - */ -export function readGooseConfig(fs: FileSystem = fsDefault, os: OS = osDefault): GooseConfig { - const defaultConfig: GooseConfig = { provider: null, model: null }; - const configPath = getConfigPath(os); - - if (!configPath) { - logger.warn('Could not determine Goose config path for this platform.'); - return defaultConfig; - } - - logger.info(`Attempting to read Goose config from: ${configPath}`); - - try { - if (!fs.existsSync(configPath)) { - logger.info('Goose config file not found at expected location.'); - return defaultConfig; - } - - const fileContents = fs.readFileSync(configPath, 'utf8'); - - // Handle both string and buffer return types - const contentStr = typeof fileContents === 'string' - ? fileContents - : fileContents.toString('utf8'); - - const config = YAML.parse(contentStr) as any; // Use 'any' for flexibility, validate below - - if (typeof config !== 'object' || config === null) { - logger.warn('Failed to parse Goose config file: Invalid YAML structure.'); - return defaultConfig; - } - - const provider = config.GOOSE_PROVIDER && typeof config.GOOSE_PROVIDER === 'string' ? config.GOOSE_PROVIDER : null; - const model = config.GOOSE_MODEL && typeof config.GOOSE_MODEL === 'string' ? config.GOOSE_MODEL : null; - - if (!provider) { - logger.warn('GOOSE_PROVIDER key not found or invalid in config file.'); - // Don't return early, still check for model - } - if (!model) { - logger.warn('GOOSE_MODEL key not found or invalid in config file.'); - } - - logger.info(`Loaded config: Provider=${provider ?? 'MISSING'}, Model=${model ?? 'MISSING'}`); - // Return nulls if keys are missing, ServerManager will handle this as an error - return { provider, model }; - - } catch (error: any) { - logger.error(`Error reading or parsing Goose config file at ${configPath}:`, error.message); - // Do not show VS Code error here, let ServerManager decide based on context - return defaultConfig; // Return nulls on error (file read/parse error) - } -} - -/** - * Gets the determined path to the configuration file. - * This is essentially a wrapper around getConfigPath for clarity. - * @param os Optional OS implementation for testing - * @returns The config file path string, or null if not found/determined. - */ -export function getConfigFilePath(os: OS = osDefault): string | null { - return getConfigPath(os); -} diff --git a/src/utils/logger.ts b/src/utils/logger.ts deleted file mode 100644 index 57b34ab..0000000 --- a/src/utils/logger.ts +++ /dev/null @@ -1,182 +0,0 @@ -import * as vscode from 'vscode'; - -/** - * Defines the available log levels. - */ -export enum LogLevel { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3, -} - -// Helper function to map config string to LogLevel enum -function mapStringToLogLevel(levelStr: string | undefined): LogLevel { - switch (levelStr?.toUpperCase()) { - case 'DEBUG': return LogLevel.DEBUG; - case 'INFO': return LogLevel.INFO; - case 'WARN': return LogLevel.WARN; - case 'ERROR': return LogLevel.ERROR; - default: return LogLevel.INFO; // Default to INFO if undefined or invalid - } -} - -/** - * Maps LogLevel enum to string representation. - */ -const LogLevelMap: { [key in LogLevel]: string } = { - [LogLevel.DEBUG]: 'DEBUG', - [LogLevel.INFO]: 'INFO', - [LogLevel.WARN]: 'WARN', - [LogLevel.ERROR]: 'ERROR', -}; - -/** - * Interface for a logger instance, potentially tagged with a source. - */ -export interface ILogger { - debug(message: string, ...args: any[]): void; - info(message: string, ...args: any[]): void; - warn(message: string, ...args: any[]): void; - error(message: string | Error, ...args: any[]): void; -} - -/** - * Central logger utility for the Goose extension. - * - * Wraps a VS Code OutputChannel and provides leveled logging methods. - * Follows a singleton pattern. - */ -export class Logger implements ILogger { - private static instance: Logger; - private outputChannel: vscode.OutputChannel; - private currentLogLevel: LogLevel = LogLevel.INFO; - private isEnabled: boolean = true; - private source: string = 'Goose'; // Default source tag - - // Private constructor for singleton and source tagging - private constructor() { - this.outputChannel = vscode.window.createOutputChannel('Goose', { log: true }); - // Read initial configuration when the instance is created - this.updateConfiguration(); - } - - /** - * Gets the singleton logger instance. - */ - public static getInstance(): Logger { - if (!Logger.instance) { - Logger.instance = new Logger(); - } - return Logger.instance; - } - - /** - * Updates the logger's configuration based on current VS Code settings. - * Call this from extension.ts on activation and config change (Task 3). - */ - public updateConfiguration(): void { - const config = vscode.workspace.getConfiguration('goose.logging'); - const newEnabled = config.get('enabled', false); - const newLevelStr = config.get('level', 'INFO'); - const newLevel = mapStringToLogLevel(newLevelStr); - - // Only log enable/disable messages if the state actually changes - if (newEnabled !== this.isEnabled) { - this.isEnabled = newEnabled; - this.log(LogLevel.INFO, this.isEnabled ? 'Logging enabled.' : 'Logging disabled.'); - } - - // Log level change if logging is enabled - if (this.isEnabled && newLevel !== this.currentLogLevel) { - this.log(LogLevel.INFO, `Log level set to: ${LogLevelMap[newLevel]}`); - } - this.currentLogLevel = newLevel; - - // Note: goose.logging.logSensitiveRequests is read elsewhere (e.g., ApiClient) - } - - /** - * Creates a new logger instance tagged with a specific source. - * Messages logged through this instance will include the source tag. - */ - public createSource(sourceName: string): Logger { - // Create a new logger instance based on the current one - const sourceLogger = Object.create(this) as Logger; - // Assign the specific source name to this new instance - sourceLogger.source = sourceName; // Store the source name - return sourceLogger; - } - - // Method to satisfy ILogger for tests or specific scenarios if needed - public getILogger(): ILogger { - return this; - } - - /** - * Updates the logger configuration based on VS Code settings. - */ - /** - * Implementation of ILogger methods (delegating to the private log method) - */ - public debug(message: string, ...args: any[]): void { - this.log(LogLevel.DEBUG, message, ...args); - } - - public info(message: string, ...args: any[]): void { - this.log(LogLevel.INFO, message, ...args); - } - - public warn(message: string, ...args: any[]): void { - this.log(LogLevel.WARN, message, ...args); - } - - public error(message: string | Error, ...args: any[]): void { - this.log(LogLevel.ERROR, message, ...args); - } - - /** - * Shows the VS Code output channel associated with this logger. - */ - public showOutputChannel(): void { - this.outputChannel.show(); - } - - /** - * Central log processing method. - */ - private log(level: LogLevel, message: string | Error, _source?: string /* Unused */, ...args: any[]): void { - // Special handling for the initial enable/disable messages from updateConfiguration - const isConfigMsg = message === 'Logging disabled.' || message.toString().startsWith('Logging enabled.') || message.toString().startsWith('Log level set to:'); - - // Check if logging is enabled and the message level is sufficient - // Allow config messages through even if the level is INFO but the setting is WARN/ERROR - if (!isConfigMsg && (!this.isEnabled || level < this.currentLogLevel)) { - return; // Skip logging if disabled or below configured level - } - - const levelStr = LogLevelMap[level]; - const timestamp = new Date().toISOString().replace('T', ' ').replace('Z', ''); // More standard format - - let formattedMessage = `[${timestamp}] [${levelStr}] [${this.source}] `; - - if (message instanceof Error) { - formattedMessage += `${message.message}${message.stack ? `\nStack: ${message.stack}` : ''}`; - } else { - formattedMessage += message; - } - - // Append additional arguments - const formattedArgs = args.map(arg => - typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg - ).join(' '); - if (formattedArgs.length > 0) { - formattedMessage += ` ${formattedArgs}`; - } - - this.outputChannel.appendLine(formattedMessage); - } -} - -// Export the singleton instance for global use -export const logger = Logger.getInstance(); diff --git a/src/utils/versionUtils.ts b/src/utils/versionUtils.ts deleted file mode 100644 index 63f20d1..0000000 --- a/src/utils/versionUtils.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as vscode from 'vscode'; -import * as fs from 'fs'; -import * as path from 'path'; -import { logger } from './logger'; - -/** - * Gets the extension version from package.json - * @returns Extension version as a string - */ -export function getExtensionVersion(): string | undefined { - try { - // Method 1: Using VS Code API - const extension = vscode.extensions.getExtension('block.vscode-goose'); - if (extension) { - return extension.packageJSON.version; - } - - // Method 2: Fallback to reading package.json directly - const packageJsonPath = path.join(__dirname, '..', '..', 'package.json'); - if (fs.existsSync(packageJsonPath)) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - return packageJson.version; - } - - // Fallback value if both methods fail - logger.warn('Could not determine extension version, using fallback value'); - return undefined; - } catch (error) { - logger.error('Error retrieving extension version:', error); - return undefined; - } -} diff --git a/src/utils/workspaceContextProvider.ts b/src/utils/workspaceContextProvider.ts deleted file mode 100644 index 4cc7303..0000000 --- a/src/utils/workspaceContextProvider.ts +++ /dev/null @@ -1,191 +0,0 @@ -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs'; -import { logger as singletonLogger } from './logger'; - -const logger = singletonLogger.createSource('WorkspaceContext'); - -export interface WorkspaceContext { - currentLanguage?: string; - projectType?: string; - currentFile?: string; - currentFilePath?: string; - diagnostics?: vscode.Diagnostic[]; - recentFiles?: string[]; - openFiles?: string[]; -} - -export class WorkspaceContextProvider { - private static instance: WorkspaceContextProvider; - private recentFiles: string[] = []; - private maxRecentFiles = 5; - - private constructor() { - // Track recently viewed files - vscode.window.onDidChangeActiveTextEditor(editor => { - if (editor && editor.document.uri.scheme === 'file') { - const filePath = editor.document.uri.fsPath; - // Remove it if it's already in the list - this.recentFiles = this.recentFiles.filter(f => f !== filePath); - // Add it to the start of the list - this.recentFiles.unshift(filePath); - // Keep only maxRecentFiles - if (this.recentFiles.length > this.maxRecentFiles) { - this.recentFiles.pop(); - } - } - }); - } - - public static getInstance(): WorkspaceContextProvider { - if (!WorkspaceContextProvider.instance) { - WorkspaceContextProvider.instance = new WorkspaceContextProvider(); - } - return WorkspaceContextProvider.instance; - } - - /** - * Get the current language of the active editor - */ - public getCurrentLanguage(): string | undefined { - const editor = vscode.window.activeTextEditor; - return editor?.document.languageId; - } - - /** - * Get the current file name - */ - public getCurrentFileName(): string | undefined { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return undefined; - } - return path.basename(editor.document.uri.fsPath); - } - - /** - * Get the current file path - */ - public getCurrentFilePath(): string | undefined { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return undefined; - } - return editor.document.uri.fsPath; - } - - /** - * Get current diagnostics (errors/warnings) for the active editor - */ - public getCurrentDiagnostics(): vscode.Diagnostic[] { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return []; - } - - return vscode.languages.getDiagnostics(editor.document.uri); - } - - /** - * Get a list of open files - */ - public getOpenFiles(): string[] { - return vscode.workspace.textDocuments - .filter(doc => doc.uri.scheme === 'file') - .map(doc => doc.uri.fsPath); - } - - /** - * Get recently viewed files - */ - public getRecentFiles(): string[] { - return [...this.recentFiles]; - } - - /** - * Get project type based on configuration files - */ - public async getProjectType(): Promise { - const workspaceFolders = vscode.workspace.workspaceFolders; - if (!workspaceFolders) { - return "unknown"; - } - - const rootPath = workspaceFolders[0].uri.fsPath; - - // Check for common project configuration files - const configFiles = [ - { file: 'package.json', type: 'node' }, - { file: 'cargo.toml', type: 'rust' }, - { file: 'pom.xml', type: 'java' }, - { file: 'requirements.txt', type: 'python' }, - { file: 'Gemfile', type: 'ruby' }, - { file: 'CMakeLists.txt', type: 'cpp' }, - { file: 'go.mod', type: 'go' }, - { file: 'Cargo.toml', type: 'rust' } - ]; - - let projectType = "unknown"; - for (const config of configFiles) { - const configPath = path.join(rootPath, config.file); - try { - if (fs.existsSync(configPath)) { - logger.debug(`Project type indicator found: ${config.file}`); - projectType = config.type; - break; // Found the type, no need to check further for this folder - } - } catch (error) { - logger.error(`Error checking for project type indicator ${config.file}:`, error); - } - } - if (projectType) { - logger.info(`Determined project type: ${projectType}`); - } - return projectType; // Return the determined type - } - - /** - * Get complete context information - */ - public async getContext(): Promise { - return { - currentLanguage: this.getCurrentLanguage(), - projectType: await this.getProjectType(), - currentFile: this.getCurrentFileName(), - currentFilePath: this.getCurrentFilePath(), - diagnostics: this.getCurrentDiagnostics(), - recentFiles: this.getRecentFiles(), - openFiles: this.getOpenFiles() - }; - } - - /** - * Format diagnostics as a string - */ - public formatDiagnostics(diagnostics: vscode.Diagnostic[]): string { - if (!diagnostics || diagnostics.length === 0) { - return "No diagnostics found"; - } - - return diagnostics.map(diag => { - const severity = this.getSeverityString(diag.severity); - const location = `Line ${diag.range.start.line + 1}, Column ${diag.range.start.character + 1}`; - return `${severity} at ${location}: ${diag.message}`; - }).join('\n'); - } - - private getSeverityString(severity: vscode.DiagnosticSeverity): string { - switch (severity) { - case vscode.DiagnosticSeverity.Error: - return 'Error'; - case vscode.DiagnosticSeverity.Warning: - return 'Warning'; - case vscode.DiagnosticSeverity.Information: - return 'Info'; - case vscode.DiagnosticSeverity.Hint: - return 'Hint'; - default: - return 'Unknown'; - } - } -} diff --git a/src/webview/App.tsx b/src/webview/App.tsx new file mode 100644 index 0000000..9badc61 --- /dev/null +++ b/src/webview/App.tsx @@ -0,0 +1,129 @@ +import { useEffect, useState } from 'react'; +import { + isStatusUpdateMessage, + isVersionStatusMessage, + VersionStatusPayload, +} from '../shared/messages'; +import { ProcessStatus } from '../shared/types'; +import { initializeBridge, onMessage } from './bridge'; +import { ChatView } from './components/chat/ChatView'; +import { SessionHeader, SessionList } from './components/session'; +import { VersionBlockedView } from './components/VersionBlockedView'; +import { useChat } from './hooks/useChat'; +import { useContextChips } from './hooks/useContextChips'; +import { useSession } from './hooks/useSession'; + +export function App() { + const [status, setStatus] = useState(ProcessStatus.STOPPED); + const [versionStatus, setVersionStatus] = useState(null); + const { + activeSession, + groupedSessions, + activeSessionId, + isPanelOpen, + isLoading, + isLoadingHistory, + historyUnavailable, + togglePanel, + closePanel, + selectSession, + createSession, + refreshSessions, + } = useSession(); + + // Keep useChat at App level so message handlers are always registered + const chat = useChat(); + + // Keep useContextChips at App level so chip messages are received even before ChatView mounts + const contextChips = useContextChips(); + + useEffect(() => { + initializeBridge(); + + const unsubscribe = onMessage(message => { + if (isStatusUpdateMessage(message)) { + setStatus(message.payload.status); + } + if (isVersionStatusMessage(message)) { + setVersionStatus(message.payload); + } + }); + + return () => { + unsubscribe(); + }; + }, []); + + const isConnected = status === ProcessStatus.RUNNING; + const isVersionBlocked = + versionStatus?.status === 'blocked_missing' || versionStatus?.status === 'blocked_outdated'; + + if (isVersionBlocked && versionStatus) { + return ( + + ); + } + + if (!isConnected) { + return ( +
+
+

+ {status === ProcessStatus.STARTING + ? 'Connecting to Goose...' + : status === ProcessStatus.ERROR + ? 'Connection error' + : 'Waiting for Goose...'} +

+

+ {status === ProcessStatus.ERROR + ? 'Please check the Goose binary path in settings' + : 'The Goose agent will start automatically'} +

+
+
+ ); + } + + return ( +
+ 0} + onHistoryClick={togglePanel} + onNewSessionClick={createSession} + /> + + {isPanelOpen ? ( + + ) : ( + <> + {isLoadingHistory && ( +
+ Loading session history... +
+ )} + {historyUnavailable && ( +
+ Session history is not available. Continue from where you left off. +
+ )} + + + )} +
+ ); +} diff --git a/src/webview/bridge.ts b/src/webview/bridge.ts new file mode 100644 index 0000000..ad0a8ec --- /dev/null +++ b/src/webview/bridge.ts @@ -0,0 +1,91 @@ +/** + * Bridge module for webview-extension communication via postMessage. + */ + +import { + AnyWebviewMessage, + createWebviewReadyMessage, + WebviewMessageType, +} from '../shared/messages'; + +declare global { + interface Window { + acquireVsCodeApi?: () => VsCodeApi; + } +} + +interface VsCodeApi { + postMessage(message: unknown): void; + getState(): unknown; + setState(state: unknown): void; +} + +const EXTENSION_VERSION = '2.0.0'; + +let vscodeApi: VsCodeApi | null = null; + +function getVsCodeApi(): VsCodeApi { + if (!vscodeApi) { + if (typeof window.acquireVsCodeApi === 'function') { + vscodeApi = window.acquireVsCodeApi(); + } else { + throw new Error('VS Code API not available'); + } + } + return vscodeApi; +} + +export function postMessage(message: AnyWebviewMessage): void { + const api = getVsCodeApi(); + api.postMessage(message); +} + +export type MessageHandler = (message: AnyWebviewMessage) => void; + +const messageHandlers: MessageHandler[] = []; + +export function onMessage(handler: MessageHandler): () => void { + messageHandlers.push(handler); + return () => { + const index = messageHandlers.indexOf(handler); + if (index > -1) { + messageHandlers.splice(index, 1); + } + }; +} + +function handleIncomingMessage(event: MessageEvent): void { + const message = event.data as AnyWebviewMessage; + for (const handler of messageHandlers) { + try { + handler(message); + } catch (err) { + console.error('Message handler error:', err); + } + } +} + +export function initializeBridge(): void { + window.addEventListener('message', handleIncomingMessage); + postMessage(createWebviewReadyMessage(EXTENSION_VERSION)); +} + +export function getState(): T | undefined { + try { + const api = getVsCodeApi(); + return api.getState() as T | undefined; + } catch { + return undefined; + } +} + +export function setState(state: T): void { + try { + const api = getVsCodeApi(); + api.setState(state); + } catch { + console.warn('Failed to set state'); + } +} + +export { WebviewMessageType }; diff --git a/src/webview/components/VersionBlockedView.tsx b/src/webview/components/VersionBlockedView.tsx new file mode 100644 index 0000000..da7603c --- /dev/null +++ b/src/webview/components/VersionBlockedView.tsx @@ -0,0 +1,140 @@ +/** + * Component for displaying version-related blocking messages. + * Shows guidance for installing or updating Goose when version requirements are not met. + */ + +import { createOpenExternalLinkMessage } from '../../shared/messages'; +import { postMessage } from '../bridge'; + +export interface VersionBlockedViewProps { + status: 'blocked_missing' | 'blocked_outdated'; + detectedVersion?: string; + minimumVersion: string; + installUrl?: string; + updateUrl?: string; +} + +export function VersionBlockedView({ + status, + detectedVersion, + minimumVersion, + installUrl, + updateUrl, +}: VersionBlockedViewProps) { + const handleLinkClick = (url: string) => { + postMessage(createOpenExternalLinkMessage(url)); + }; + + if (status === 'blocked_missing') { + return ( +
+
+ +

+ Welcome to Goose +

+

+ To get started, you need to install Goose (version {minimumVersion} or higher) on your + system. +

+ {installUrl && ( + + )} +
+
+ ); + } + + return ( +
+
+ +

+ Goose Update Required +

+

+ This extension requires Goose version {minimumVersion} or higher. +

+ {detectedVersion && ( +

+ Your current version:{' '} + {detectedVersion} +

+ )} + {updateUrl && ( + + )} +
+
+ ); +} + +function GooseIcon({ className }: { className?: string }) { + return ( + + ); +} + +function UpdateIcon({ className }: { className?: string }) { + return ( + + ); +} + +function ExternalLinkIcon({ className }: { className?: string }) { + return ( + + ); +} diff --git a/src/webview/components/chat/AssistantMessage.tsx b/src/webview/components/chat/AssistantMessage.tsx new file mode 100644 index 0000000..a871a75 --- /dev/null +++ b/src/webview/components/chat/AssistantMessage.tsx @@ -0,0 +1,109 @@ +import { useState } from 'react'; +import { parseContent } from '../../../shared/fileReferenceParser'; +import { MessageStatus } from '../../../shared/types'; +import { CopyButton } from '../markdown/CopyButton'; +import { MarkdownRenderer } from '../markdown/MarkdownRenderer'; +import { FileReferenceCard } from './FileReferenceCard'; +import { ProgressIndicator } from './ProgressIndicator'; + +const MAX_LINES_COLLAPSED = 15; + +interface AssistantMessageProps { + content: string; + timestamp?: Date; + status: MessageStatus; + isStreaming: boolean; +} + +function formatTime(date?: Date): string { + if (!date) return 'Earlier'; + return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); +} + +function truncateContent( + content: string, + maxLines: number +): { truncated: string; isTruncated: boolean; totalLines: number } { + const lines = content.split('\n'); + if (lines.length <= maxLines) { + return { truncated: content, isTruncated: false, totalLines: lines.length }; + } + return { + truncated: lines.slice(0, maxLines).join('\n') + '\n...', + isTruncated: true, + totalLines: lines.length, + }; +} + +export function AssistantMessage({ + content, + timestamp, + status, + isStreaming, +}: AssistantMessageProps) { + const [isExpanded, setIsExpanded] = useState(false); + const showIndicator = isStreaming && !content; + + // Parse content to check if it's a file reference + // Only parse when not streaming to avoid partial matches + const parsedContent = !isStreaming ? parseContent(content) : { type: 'text' as const, content }; + const isFileReference = parsedContent.type === 'file_reference'; + + // Only truncate history messages (no timestamp) that aren't streaming and aren't file references + const isHistoryMessage = !timestamp; + const { truncated, isTruncated, totalLines } = truncateContent(content, MAX_LINES_COLLAPSED); + const displayContent = + isHistoryMessage && isTruncated && !isExpanded && !isStreaming && !isFileReference + ? truncated + : content; + + // Get copy text - for file references, use the file content if available + const copyText = + isFileReference && parsedContent.type === 'file_reference' + ? parsedContent.reference.content || content + : content; + + return ( +
+
+
+ {showIndicator ? ( + + ) : isFileReference && parsedContent.type === 'file_reference' ? ( + + ) : ( + <> + + {isHistoryMessage && isTruncated && !isStreaming && ( + + )} + + )} + {!isStreaming && content && ( +
+ +
+ )} +
+
+

+ {formatTime(timestamp)} +

+ {status === MessageStatus.CANCELLED && ( + + (cancelled) + + )} +
+
+
+ ); +} diff --git a/src/webview/components/chat/ChatContainer.tsx b/src/webview/components/chat/ChatContainer.tsx new file mode 100644 index 0000000..f202686 --- /dev/null +++ b/src/webview/components/chat/ChatContainer.tsx @@ -0,0 +1,63 @@ +import { useCallback, useRef } from 'react'; +import { useChat } from '../../hooks/useChat'; +import { useKeyboardNav } from '../../hooks/useKeyboardNav'; +import { InputArea } from './InputArea'; +import { MessageList, MessageListHandle } from './MessageList'; + +interface ChatContainerProps { + className?: string; +} + +export function ChatContainer({ className = '' }: ChatContainerProps) { + const { + messages, + isGenerating, + inputValue, + setInputValue, + sendMessage, + stopGeneration, + focusedIndex, + setFocusedIndex, + retryMessage, + } = useChat(); + + const messageListRef = useRef(null); + + const scrollToMessage = useCallback((index: number) => { + messageListRef.current?.scrollToMessage(index); + }, []); + + useKeyboardNav({ + messageCount: messages.length, + focusedIndex, + setFocusedIndex, + scrollToMessage, + isGenerating, + onStopGeneration: stopGeneration, + }); + + return ( +
+ + +
+ ); +} diff --git a/src/webview/components/chat/ChatView.tsx b/src/webview/components/chat/ChatView.tsx new file mode 100644 index 0000000..58d0cbe --- /dev/null +++ b/src/webview/components/chat/ChatView.tsx @@ -0,0 +1,106 @@ +import { useCallback, useRef } from 'react'; +import type { ContextChip, FileSearchResult } from '../../../shared/contextTypes'; +import { UseChatReturn } from '../../hooks/useChat'; +import { UseContextChipsReturn } from '../../hooks/useContextChips'; +import { useKeyboardNav } from '../../hooks/useKeyboardNav'; +import { InputArea } from './InputArea'; +import { MessageList, MessageListHandle } from './MessageList'; + +interface ChatViewProps { + className?: string; + chat: UseChatReturn; + contextChips: UseContextChipsReturn; +} + +export function ChatView({ className = '', chat, contextChips }: ChatViewProps) { + const { + messages, + isGenerating, + inputValue, + setInputValue, + sendMessage, + stopGeneration, + focusedIndex, + setFocusedIndex, + retryMessage, + } = chat; + + const { + chips, + addChip, + removeChip, + clearChips, + focusedIndex: chipFocusedIndex, + setFocusedIndex: setChipFocusedIndex, + hasDuplicate, + announcement: chipAnnouncement, + } = contextChips; + + const messageListRef = useRef(null); + + // Convert FileSearchResult to ContextChip and add it + const handleAddFileChip = useCallback( + (result: FileSearchResult) => { + // Check for duplicates (file without line range) + if (hasDuplicate(result.path)) { + return; + } + + const chip: ContextChip = { + id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, + filePath: result.path, + fileName: result.fileName, + languageId: result.languageId, + // No range for @ picker selections (whole file) + }; + + addChip(chip); + }, + [addChip, hasDuplicate] + ); + + const scrollToMessage = useCallback((index: number) => { + messageListRef.current?.scrollToMessage(index); + }, []); + + useKeyboardNav({ + messageCount: messages.length, + focusedIndex, + setFocusedIndex, + scrollToMessage, + isGenerating, + onStopGeneration: stopGeneration, + }); + + return ( +
+ + +
+ ); +} diff --git a/src/webview/components/chat/ChipStack.tsx b/src/webview/components/chat/ChipStack.tsx new file mode 100644 index 0000000..c970a27 --- /dev/null +++ b/src/webview/components/chat/ChipStack.tsx @@ -0,0 +1,167 @@ +import { useCallback, useEffect, useRef } from 'react'; +import type { ContextChip as ContextChipType } from '../../../shared/contextTypes'; +import { ContextChip } from './ContextChip'; + +interface ChipStackProps { + chips: readonly ContextChipType[]; + focusedIndex: number | null; + onRemove: (chipId: string) => void; + onFocusChange: (index: number | null) => void; + announcement?: string | null; +} + +/** + * Container for displaying context chips in a horizontal flex wrap layout. + * Manages keyboard navigation between chips using arrow keys. + */ +export function ChipStack({ + chips, + focusedIndex, + onRemove, + onFocusChange, + announcement, +}: ChipStackProps) { + const containerRef = useRef(null); + const chipRefs = useRef>(new Map()); + + // Focus the chip element when focusedIndex changes + useEffect(() => { + if (focusedIndex !== null && chipRefs.current.has(focusedIndex)) { + chipRefs.current.get(focusedIndex)?.focus(); + } + }, [focusedIndex]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (chips.length === 0) return; + + switch (e.key) { + case 'ArrowLeft': { + e.preventDefault(); + if (focusedIndex === null) { + // Focus last chip + onFocusChange(chips.length - 1); + } else if (focusedIndex > 0) { + // Move focus left + onFocusChange(focusedIndex - 1); + } + break; + } + case 'ArrowRight': { + e.preventDefault(); + if (focusedIndex !== null && focusedIndex < chips.length - 1) { + // Move focus right + onFocusChange(focusedIndex + 1); + } else if (focusedIndex === chips.length - 1) { + // At last chip, clear focus (move to input) + onFocusChange(null); + } + break; + } + case 'Delete': + case 'Backspace': { + if (focusedIndex !== null) { + e.preventDefault(); + const chipToRemove = chips[focusedIndex]; + if (chipToRemove) { + // Determine new focus after removal + const newFocusIndex = + focusedIndex >= chips.length - 1 + ? chips.length > 1 + ? focusedIndex - 1 + : null + : focusedIndex; + onRemove(chipToRemove.id); + onFocusChange(newFocusIndex); + } + } + break; + } + case 'Tab': { + // Tab moves focus out of chips to the input + // Let the event propagate naturally + if (focusedIndex !== null) { + onFocusChange(null); + } + break; + } + case 'Escape': { + // Clear chip focus + if (focusedIndex !== null) { + e.preventDefault(); + onFocusChange(null); + } + break; + } + } + }, + [chips, focusedIndex, onFocusChange, onRemove] + ); + + const handleChipRemove = useCallback( + (chipId: string, index: number) => { + // Determine new focus after removal + const newFocusIndex = + index >= chips.length - 1 ? (chips.length > 1 ? index - 1 : null) : index; + onRemove(chipId); + onFocusChange(newFocusIndex); + }, + [chips.length, onRemove, onFocusChange] + ); + + const handleChipFocus = useCallback( + (index: number) => { + onFocusChange(index); + }, + [onFocusChange] + ); + + const setChipRef = useCallback( + (index: number) => (el: HTMLElement | null) => { + if (el) { + chipRefs.current.set(index, el); + } else { + chipRefs.current.delete(index); + } + }, + [] + ); + + // Don't render if no chips (but still render aria-live for announcements) + if (chips.length === 0) { + // Render just the aria-live region for removal announcements + return announcement ? ( +
+ {announcement} +
+ ) : null; + } + + return ( +
+ {/* Screen reader announcements */} +
+ {announcement} +
+ {chips.map((chip, index) => ( +
+ handleChipRemove(chip.id, index)} + onFocus={() => handleChipFocus(index)} + /> +
+ ))} +
+ ); +} diff --git a/src/webview/components/chat/ContextChip.tsx b/src/webview/components/chat/ContextChip.tsx new file mode 100644 index 0000000..f89b543 --- /dev/null +++ b/src/webview/components/chat/ContextChip.tsx @@ -0,0 +1,86 @@ +import type { ContextChip as ContextChipType } from '../../../shared/contextTypes'; +import { FileTypeIcon } from '../icons/FileTypeIcon'; + +interface ContextChipProps { + chip: ContextChipType; + isFocused: boolean; + onRemove: () => void; + onFocus: () => void; +} + +/** + * Individual chip displaying a file reference with optional line range. + * Used for showing context selections in the chat input area. + */ +export function ContextChip({ chip, isFocused, onRemove, onFocus }: ContextChipProps) { + const displayText = chip.range + ? `${chip.fileName}:${chip.range.startLine}-${chip.range.endLine}` + : chip.fileName; + + const handleRemoveClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onRemove(); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Delete' || e.key === 'Backspace') { + e.preventDefault(); + onRemove(); + } + }; + + return ( + + + {displayText} + + + ); +} + +interface CloseIconProps { + className?: string; +} + +function CloseIcon({ className }: CloseIconProps) { + return ( + + + + ); +} diff --git a/src/webview/components/chat/ErrorMessage.tsx b/src/webview/components/chat/ErrorMessage.tsx new file mode 100644 index 0000000..8f7bc4b --- /dev/null +++ b/src/webview/components/chat/ErrorMessage.tsx @@ -0,0 +1,81 @@ +interface ErrorMessageProps { + content: string; + timestamp?: Date; + onRetry?: (content: string) => void; + originalContent?: string; +} + +function formatTime(date?: Date): string { + if (!date) return 'Earlier'; + return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); +} + +export function ErrorMessage({ content, timestamp, onRetry, originalContent }: ErrorMessageProps) { + const canRetry = onRetry && originalContent; + + return ( +
+
+
+ +
+

{content}

+ {canRetry && ( + + )} +
+
+
+

+ {formatTime(timestamp)} +

+
+ ); +} + +function ErrorIcon({ className }: { className?: string }) { + return ( + + ); +} + +function RetryIcon({ className }: { className?: string }) { + return ( + + ); +} diff --git a/src/webview/components/chat/FileReferenceCard.tsx b/src/webview/components/chat/FileReferenceCard.tsx new file mode 100644 index 0000000..b13a02b --- /dev/null +++ b/src/webview/components/chat/FileReferenceCard.tsx @@ -0,0 +1,115 @@ +/** + * File reference card component for displaying file content from Goose. + * Renders as a collapsible chip - click to expand and see file content. + */ + +import { useState } from 'react'; +import type { ParsedFileReference } from '../../../shared/fileReferenceParser'; +import { getLanguageFromPath } from '../../../shared/fileReferenceParser'; +import { FileTypeIcon } from '../icons/FileTypeIcon'; +import { CodeBlock } from '../markdown/CodeBlock'; + +interface FileReferenceCardProps { + reference: ParsedFileReference; +} + +/** + * Displays a file reference as a collapsible chip. + * Click to expand and see file content. + */ +export function FileReferenceCard({ reference }: FileReferenceCardProps) { + const [isExpanded, setIsExpanded] = useState(false); + const { filePath, fileName, content, language } = reference; + const languageId = language || getLanguageFromPath(filePath); + const hasContent = content && content.trim().length > 0; + + return ( +
+ {/* Collapsible file chip */} + + + {/* Expanded content */} + {isExpanded && ( +
+ {/* Full file path - selectable */} +
+ {filePath} +
+ {/* Code content */} + {hasContent && ( +
+ +
+ )} +
+ )} +
+ ); +} + +function ChevronIcon({ expanded, className }: { expanded: boolean; className?: string }) { + return ( + + + + ); +} + +/** + * Extracts the directory path from a full file path. + */ +function getDirectoryPath(filePath: string): string { + const parts = filePath.split(/[/\\]/); + parts.pop(); // Remove filename + const dir = parts.join('/'); + + // Shorten long paths + if (dir.length > 40) { + return '...' + dir.slice(-37); + } + return dir; +} diff --git a/src/webview/components/chat/InputArea.tsx b/src/webview/components/chat/InputArea.tsx new file mode 100644 index 0000000..f1a8969 --- /dev/null +++ b/src/webview/components/chat/InputArea.tsx @@ -0,0 +1,234 @@ +import { KeyboardEvent, useCallback, useEffect, useRef } from 'react'; +import type { ContextChip, FileSearchResult } from '../../../shared/contextTypes'; +import { isFocusChatInputMessage } from '../../../shared/messages'; +import { onMessage } from '../../bridge'; +import { useFilePicker } from '../../hooks/useFilePicker'; +import { FilePicker } from '../picker/FilePicker'; +import { ChipStack } from './ChipStack'; +import { SendButton } from './SendButton'; +import { StopButton } from './StopButton'; + +interface InputAreaProps { + value: string; + onChange: (value: string) => void; + onSend: (chips?: readonly ContextChip[]) => void; + onStop: () => void; + isGenerating: boolean; + disabled: boolean; + chips?: readonly ContextChip[]; + onRemoveChip?: (chipId: string) => void; + chipFocusedIndex?: number | null; + onChipFocusChange?: (index: number | null) => void; + onClearChips?: () => void; + onAddFileChip?: (result: FileSearchResult) => void; + chipAnnouncement?: string | null; +} + +const MAX_HEIGHT = 200; +const isMac = typeof navigator !== 'undefined' && /Mac/.test(navigator.platform); +const PLACEHOLDER = `Message Goose... (${isMac ? '⌘' : 'Ctrl'}+↑/↓ to navigate)`; + +export function InputArea({ + value, + onChange, + onSend, + onStop, + isGenerating, + disabled, + chips = [], + onRemoveChip, + chipFocusedIndex = null, + onChipFocusChange, + onClearChips, + onAddFileChip, + chipAnnouncement = null, +}: InputAreaProps) { + const textareaRef = useRef(null); + const containerRef = useRef(null); + + // Initialize file picker hook + // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional noop + const noopAddChip = useCallback(() => {}, []); + const filePicker = useFilePicker(onAddFileChip ?? noopAddChip, textareaRef, onChange); + + // biome-ignore lint/correctness/useExhaustiveDependencies: value triggers textarea resize + useEffect(() => { + const textarea = textareaRef.current; + if (!textarea) return; + + textarea.style.height = 'auto'; + const newHeight = Math.min(textarea.scrollHeight, MAX_HEIGHT); + textarea.style.height = `${newHeight}px`; + }, [value]); + + // Click outside handler to close file picker + useEffect(() => { + if (!filePicker.isOpen) return; + + const handleClickOutside = (e: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(e.target as Node)) { + filePicker.close(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + // eslint-disable-next-line react-hooks/exhaustive-deps -- only re-run when isOpen or close changes, not entire filePicker object + }, [filePicker.isOpen, filePicker.close]); + + // Subscribe to FOCUS_CHAT_INPUT message from extension + useEffect(() => { + const unsubscribe = onMessage(message => { + if (isFocusChatInputMessage(message)) { + textareaRef.current?.focus(); + } + }); + return unsubscribe; + }, []); + + const handleSend = useCallback(() => { + if (disabled || isGenerating) return; + + const userInput = value.trim(); + const hasChips = chips.length > 0; + + if (!userInput && !hasChips) return; + + // Pass chips to onSend - extension will handle formatting + onSend(hasChips ? chips : undefined); + onChange(''); + onClearChips?.(); + }, [value, disabled, isGenerating, chips, onChange, onSend, onClearChips]); + + const handleKeyDown = (e: KeyboardEvent) => { + // Pass keyboard events to file picker first + if (filePicker.handleKeyDown(e)) { + return; + } + + const textarea = textareaRef.current; + const cursorAtStart = textarea?.selectionStart === 0 && textarea?.selectionEnd === 0; + + // Handle keyboard navigation to chips + if (chips.length > 0 && onChipFocusChange) { + // Backspace at empty input or cursor at position 0 focuses last chip + if (e.key === 'Backspace' && (value === '' || cursorAtStart)) { + e.preventDefault(); + onChipFocusChange(chips.length - 1); + return; + } + + // Arrow left at input start moves focus to chip area + if (e.key === 'ArrowLeft' && cursorAtStart) { + e.preventDefault(); + onChipFocusChange(chips.length - 1); + return; + } + } + + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + // Can send if there's user input OR if there are chips (context to send) + const canSend = !disabled && !isGenerating && (value.trim().length > 0 || chips.length > 0); + + // Focus input when chip focus is cleared (e.g., after ArrowRight from last chip or Tab) + useEffect(() => { + if (chipFocusedIndex === null && chips.length > 0) { + // Only refocus if we had chips (indicating navigation occurred) + // This is handled by the ChipStack component setting focusedIndex to null + } + }, [chipFocusedIndex, chips.length]); + + const handleRemoveChip = useCallback( + (chipId: string) => { + onRemoveChip?.(chipId); + }, + [onRemoveChip] + ); + + const handleChipFocusChange = useCallback( + (index: number | null) => { + onChipFocusChange?.(index); + // If focus is moving out of chips (index is null), focus the textarea + if (index === null) { + textareaRef.current?.focus(); + } + }, + [onChipFocusChange] + ); + + // Handle input change - update value and trigger @ detection + const handleInputChange = useCallback( + (e: React.ChangeEvent) => { + const newValue = e.target.value; + const cursorPosition = e.target.selectionStart ?? 0; + + onChange(newValue); + filePicker.handleInput(newValue, cursorPosition); + }, + [onChange, filePicker] + ); + + // File picker navigation handlers + const handleFilePickerNavigate = useCallback( + (direction: 'up' | 'down') => { + const e = { + key: direction === 'up' ? 'ArrowUp' : 'ArrowDown', + // biome-ignore lint/suspicious/noEmptyBlockStatements: mock event handler + preventDefault: () => {}, + } as React.KeyboardEvent; + filePicker.handleKeyDown(e); + }, + [filePicker] + ); + + return ( +
+ {/* Render ChipStack above the input area when chips exist, or show announcements */} + {(chips.length > 0 || chipAnnouncement) && onRemoveChip && onChipFocusChange && ( + + )} +
+
+ {/* File picker dropdown */} + +