diff --git a/sample-apps/Ctrl Z/.gitignore b/sample-apps/Ctrl Z/.gitignore new file mode 100644 index 000000000..3f6d959a5 --- /dev/null +++ b/sample-apps/Ctrl Z/.gitignore @@ -0,0 +1,31 @@ +# ESP-IDF +build/ +managed_components/ +sdkconfig +sdkconfig.old +dependencies.lock + +# IDE +.vscode/ +.idea/ + +# Python +__pycache__/ +*.pyc + +# Logs +*.log + +# OS +Thumbs.db +.DS_Store + +# Environment +.env + +# Guardian Bridge output +backend/guardian_bridge/guardian_data.jsonl + +# Python cache +__pycache__/ +*.pyc \ No newline at end of file diff --git a/sample-apps/Ctrl Z/README.md b/sample-apps/Ctrl Z/README.md new file mode 100644 index 000000000..5c2c8916d --- /dev/null +++ b/sample-apps/Ctrl Z/README.md @@ -0,0 +1,24 @@ +# GuardianSense AI + +GuardianSense AI is an ambient wellness monitoring system built using ESP32 CSI and NitroStack. + +## Hardware + +- ESP32 DevKit V1 (CSI Sender) +- ESP32-S3 (CSI Receiver) + +## Current Status + +- ✅ ESP-IDF configured +- ✅ CSI Sender working +- ✅ CSI Receiver working +- ✅ Live CSI packets verified + +## Next Milestones + +- CSI Parser +- Motion Detection +- Respiration Estimation +- Guardian Core Engine +- NitroStack Integration +- Dashboard diff --git a/sample-apps/Ctrl Z/ai/.gitkeep b/sample-apps/Ctrl Z/ai/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/sample-apps/Ctrl Z/architecture/.gitkeep b/sample-apps/Ctrl Z/architecture/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/sample-apps/Ctrl Z/backend/.gitkeep b/sample-apps/Ctrl Z/backend/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/auth-security/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/auth-security/SKILL.md @@ -0,0 +1,141 @@ +--- +name: nitrostack-auth-security +description: Best practices for implementing JWT, API Keys, OAuth 2.1, and RBAC in a NitroStack application. +--- + +## When to Use +Use this skill when configuring security modules, implementing user authentication, restricting tool access via guards, or handling sensitive tokens. + +--- + +## 1. JSON Web Tokens (JWT) +To secure tools with JWT authentication: + +### Register `JWTModule`: +```typescript +import { JWTModule, Module, McpApp } from '@nitrostack/core'; + +@McpApp({ + server: { name: 'my-server', version: '1.0.0' } +}) +@Module({ + imports: [ + JWTModule.forRoot({ + secret: process.env.JWT_SECRET!, + expiresIn: '7d', + }), + ] +}) +export class AppModule {} +``` + +### Write a `JWTGuard`: +```typescript +import { Guard, ExecutionContext, Injectable, ConfigService } from '@nitrostack/core'; +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JWTGuard implements Guard { + constructor(private config: ConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const token = this.extractToken(context); + if (!token) return false; + + try { + const secret = this.config.get('JWT_SECRET'); + const payload = jwt.verify(token, secret) as any; + context.auth = { + subject: payload.sub, + role: payload.role, + token, + }; + return true; + } catch { + return false; + } + } + + private extractToken(context: ExecutionContext): string | null { + const auth = context.metadata?.authorization; + if (auth?.startsWith('Bearer ')) { + return auth.substring(7); + } + return null; + } +} +``` + +--- + +## 2. API Key Authentication +Use `ApiKeyModule` for service-to-service validation. + +### Register `ApiKeyModule`: +```typescript +import { ApiKeyModule, Module } from '@nitrostack/core'; + +@Module({ + imports: [ + ApiKeyModule.forRoot({ + keysEnvPrefix: 'API_KEY', // Reads API_KEY_1, API_KEY_2, etc. + headerName: 'x-api-key', + hashed: false, + }), + ] +}) +export class AppModule {} +``` + +### API Key Guard: +```typescript +import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core'; + +export class ApiKeyGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const apiKey = context.metadata?.['x-api-key'] || context.metadata?.apiKey; + if (!apiKey) return false; + + const isValid = await ApiKeyModule.validate(apiKey as string); + if (isValid) { + context.auth = { + subject: `apikey_${(apiKey as string).substring(0, 10)}`, + scopes: ['*'], + }; + return true; + } + return false; + } +} +``` + +--- + +## 3. Role-Based Access Control (RBAC) +Chain guards sequentially to implement user-role authorization. + +```typescript +import { Injectable, Guard, ExecutionContext, UseGuards, Tool, z } from '@nitrostack/core'; +import { JWTGuard } from './jwt.guard.js'; + +@Injectable() +export class AdminGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + // Requires JWTGuard to have populated context.auth first + return context.auth?.role === 'admin'; + } +} + +// Applying chained guards to a tool +export class SystemTools { + @Tool({ + name: 'reset_database', + description: 'Dangerous action: wipes database. Admin only.', + inputSchema: z.object({}), + }) + @UseGuards(JWTGuard, AdminGuard) // Chain auth first, then role check + async resetDatabase() { + return { success: true }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/mcp-app-architecture/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/mcp-app-architecture/SKILL.md @@ -0,0 +1,176 @@ +--- +name: nitrostack-mcp-app-architecture +description: Best practices and guidelines for bootstrapping, defining modules, using dependency injection, managing server lifecycles, and handling events in the NitroStack SDK. +--- + +## When to Use +Use this skill whenever you are bootstrapping a new NitroStack MCP server, creating modules, injecting services, or handling application lifecycle events. + +## Bootstrapping a NitroStack App +A NitroStack application is initialized with the `@McpApp` decorator on a root class, accompanied by a root `@Module`. + +```typescript +import { McpApp, Module } from '@nitrostack/core'; +import { DatabaseModule } from './database/database.module.js'; +import { UsersModule } from './users/users.module.js'; + +@McpApp({ + module: AppModule, + server: { + name: 'user-management-server', + version: '1.0.0', + }, +}) +@Module({ + imports: [DatabaseModule, UsersModule], +}) +export class AppModule {} +``` + +## Modules +Modules organize your application structure. Use the `@Module` decorator to define imports, exports, and providers. + +* **`imports`**: Other modules whose exported providers should be available in this module. +* **`providers`**: Services, tools, resources, or prompts that should be instantiated and managed by the DI container within this module. +* **`exports`**: Providers defined in this module that should be visible to other modules importing this one. + +```typescript +import { Module } from '@nitrostack/core'; +import { UsersService } from './users.service.js'; +import { UsersTools } from './users.tools.js'; + +@Module({ + providers: [UsersService, UsersTools], + exports: [UsersService], +}) + +## Controllers +Use the `@ControllerDecorator` (or alias it as `@Controller`) to group tools, resources, and prompts together. Controllers are automatically registered as singletons in the DI container. + +### Key Controller Options: +* **`prefix`**: A string prefix applied to every `@Tool` defined in this controller. For example, `@ControllerDecorator('github')` prefixing a tool named `create_issue` exposes it to MCP clients as `github_create_issue`. + +```typescript +import { ControllerDecorator as Controller, Tool, ExecutionContext } from '@nitrostack/core'; + +@Controller('github') +export class GitHubController { + @Tool({ + name: 'create_issue', + description: 'Create an issue in a repository', + inputSchema: z.object({ /* ... */ }) + }) + async createIssue(input: any, ctx: ExecutionContext) { + // Exposed to clients as "github_create_issue" + } +} +``` + +## Dependency Injection (DI) +NitroStack uses a robust dependency injection container to manage class instances and lifecycles. + +### Injection Lifecycles +1. **Singleton (Default)**: A single instance is shared across the entire application. +2. **Transient**: A new instance is created every time it is resolved/injected. +3. **Scoped**: A new instance is created per incoming request or context. + +```typescript +import { Injectable, Scope } from '@nitrostack/core'; + +@Injectable({ scope: Scope.SINGLETON }) +export class UsersService { + constructor(private readonly db: DatabaseService) {} + + async getUser(id: string) { + return this.db.query('SELECT * FROM users WHERE id = $1', [id]); + } +} +``` + +## Lifecycles and Hooks +Implement NestJS-style lifecycle interfaces on modules, controllers, or providers to hook into application state changes: + +* **`OnModuleInit`** (`onModuleInit`): Called after modules have initialized but before the server starts listening. +* **`OnApplicationBootstrap`** (`onApplicationBootstrap`): Called once the server is fully started and listening. +* **`OnModuleDestroy`** (`onModuleDestroy`): Called when the module or application is shutting down. +* **`BeforeApplicationShutdown`** (`beforeApplicationShutdown(signal?: string)`): Called before the application starts shutting down. Receives the OS signal (e.g. `SIGINT`). +* **`OnApplicationShutdown`** (`onApplicationShutdown(signal?: string)`): Called during shutdown. Receives the OS signal. + +```typescript +import { + Injectable, + OnModuleInit, + OnApplicationBootstrap, + OnModuleDestroy, + BeforeApplicationShutdown, + OnApplicationShutdown +} from '@nitrostack/core'; + +@Injectable() +export class DatabaseService + implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown +{ + async onModuleInit() { + await this.connect(); + } + + async onApplicationBootstrap() { + console.log('App ready to handle connections.'); + } + + async onModuleDestroy() { + await this.cleanupPendingQueries(); + } + + async beforeApplicationShutdown(signal?: string) { + console.log(`Shutting down soon (signal: ${signal}).`); + } + + async onApplicationShutdown(signal?: string) { + await this.disconnect(); + } +} +``` + +--- + +## Eventing System (`emitEvent` and `@OnEvent`) +NitroStack includes an internal eventing system to decouple components. A service or tool can emit an event using `emitEvent`, and any injectable class (like a handler service or controller) can subscribe using the `@OnEvent` decorator. + +### 1. Emitting Events +Call `emitEvent` to dispatch an event payload asynchronously. + +```typescript +import { Injectable, emitEvent } from '@nitrostack/core'; + +@Injectable() +export class SpaceShipService { + async launchShip(shipId: string) { + // Process launch... + + // Dispatch event + emitEvent('ship.launched', { + shipId, + timestamp: new Date().toISOString(), + }); + } +} +``` + +### 2. Listening to Events +Decorate a method inside any `@Injectable()` class with `@OnEvent('event_pattern')` to register it as an event handler. + +```typescript +import { Injectable, OnEvent } from '@nitrostack/core'; + +@Injectable({ deps: [] }) +export class FlightLogHandler { + @OnEvent('ship.launched') + async logLaunch(data: { shipId: string; timestamp: string }) { + console.error(`🚀 [EVENT] Ship ${data.shipId} was successfully launched at ${data.timestamp}`); + } +} +``` + +> [!NOTE] +> For the `@OnEvent` decorator to register properly, the containing class must be declared as a provider inside an active module. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/middleware-pipeline/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/middleware-pipeline/SKILL.md @@ -0,0 +1,235 @@ +--- +name: nitrostack-middleware-pipeline +description: Best practices for implementing and applying Guards, Interceptors, Middleware, Pipes, and Exception Filters in the NitroStack SDK. +--- + +## When to Use +Use this skill when implementing request validation, authorization checks, response mapping, logging, error handling, or performance tracking on NitroStack tool methods. + +--- + +## 1. Guards (`Guard` and `@UseGuards`) +Guards determine if a request should be processed by a tool handler based on authentication, authorization, or other conditions. + +### Interface: +```typescript +import { Guard, ExecutionContext } from '@nitrostack/core'; + +export interface Guard { + canActivate(context: ExecutionContext): boolean | Promise; +} +``` + +### Example: +```typescript +import { Guard, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class RolesGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const userRoles = context.clientMetadata?.roles || []; + return userRoles.includes('admin'); + } +} +``` + +Apply the guard using `@UseGuards(...)`: +```typescript +import { Tool, UseGuards, z } from '@nitrostack/core'; +import { RolesGuard } from './roles.guard.js'; + +export class AdminTools { + @Tool({ + name: 'delete_system_logs', + description: 'Delete all system logs from the server.', + inputSchema: z.object({}), + }) + @UseGuards(RolesGuard) + async deleteLogs() { + return { success: true }; + } +} +``` + +--- + +## 2. Interceptors (`InterceptorInterface` and `@UseInterceptors`) +Interceptors can transform/intercept input arguments or mapped output from a tool method execution. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface InterceptorInterface { + intercept(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { InterceptorInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class TimingInterceptor implements InterceptorInterface { + async intercept(context: ExecutionContext, next: () => Promise): Promise { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + context.logger.info(`Execution took ${duration}ms`); + return { + ...result, + _meta: { durationMs: duration } + }; + } +} +``` + +--- + +## 3. Exception Filters (`ExceptionFilterInterface` and `@UseFilters`) +Exception filters catch any errors thrown within guards, interceptors, or the tool handlers themselves, mapping them into user-friendly JSON payloads. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext): unknown | Promise; +} +``` + +### Example: +```typescript +import { ExceptionFilterInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class CustomExceptionFilter implements ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext) { + const message = exception instanceof Error ? exception.message : 'Unknown error'; + return { + error: true, + message, + timestamp: new Date().toISOString() + }; + } +} +``` + +Apply the filter using `@UseFilters(...)` on a tool method: + +```typescript +import { Tool, UseFilters, z } from '@nitrostack/core'; +import { CustomExceptionFilter } from './custom-exception.filter.js'; + +export class LoggingTools { + @Tool({ + name: 'generate_report', + description: 'Generates system usage reports.', + inputSchema: z.object({}), + }) + @UseFilters(CustomExceptionFilter) + async generateReport() { + throw new Error('Report generation is not implemented yet.'); + } +} +``` + +--- + +## 4. Middleware (`MiddlewareInterface`, `@Middleware` and `@UseMiddleware`) +Middleware executes before the request reaches the tool handler, and can wrap the handler execution by invoking `next()`. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface MiddlewareInterface { + use(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { Middleware, MiddlewareInterface, ExecutionContext } from '@nitrostack/core'; + +@Middleware() +export class LoggingMiddleware implements MiddlewareInterface { + async use(context: ExecutionContext, next: () => Promise): Promise { + context.logger.info(`Entering tool: ${context.toolName}`); + try { + const result = await next(); + context.logger.info(`Exiting tool: ${context.toolName}`); + return result; + } catch (error) { + context.logger.error(`Error in tool: ${error}`); + throw error; + } + } +} +``` + +Apply the middleware using `@UseMiddleware(...)` on a tool method: +```typescript +import { Tool, UseMiddleware, z } from '@nitrostack/core'; +import { LoggingMiddleware } from './logging.middleware.js'; + +export class StationTools { + @Tool({ + name: 'fetch_logs', + description: 'Fetch station operations logs.', + inputSchema: z.object({}), + }) + @UseMiddleware(LoggingMiddleware) + async fetchLogs() { + return { status: 'operational' }; + } +} +``` + +--- + +## 5. Pipes (`PipeInterface`, `@Pipe` and `@UsePipes`) +Pipes are used to transform or validate input arguments before they reach the tool handler method. + +### Interface: +```typescript +import { ArgumentMetadata } from '@nitrostack/core'; + +export interface PipeInterface { + transform(value: T, metadata: ArgumentMetadata): R | Promise; +} +``` + +### Example: +```typescript +import { Pipe, PipeInterface, ArgumentMetadata } from '@nitrostack/core'; + +@Pipe() +export class TrimPipe implements PipeInterface, Record> { + transform(value: Record, metadata: ArgumentMetadata) { + const trimmed: Record = {}; + for (const [key, val] of Object.entries(value)) { + trimmed[key] = typeof val === 'string' ? val.trim() : val; + } + return trimmed; + } +} +``` + +Apply the pipe using `@UsePipes(...)` on a tool method: +```typescript +import { Tool, UsePipes, z } from '@nitrostack/core'; +import { TrimPipe } from './trim.pipe.js'; + +export class MessagingTools { + @Tool({ + name: 'send_message', + description: 'Send a message to other stations.', + inputSchema: z.object({ text: z.string() }), + }) + @UsePipes(TrimPipe) + async sendMessage(input: { text: string }) { + return { sentText: input.text }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/tools-resources-prompts/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/tools-resources-prompts/SKILL.md @@ -0,0 +1,287 @@ +--- +name: nitrostack-tools-resources-prompts +description: Guidelines and patterns for defining Tools, Resources, and Prompts in a NitroStack application with schema validation via Zod, including caching, rate-limiting, and base64 file uploads. +--- + +## When to Use +Use this skill whenever you are defining, editing, or validating tools, resources, or prompts on a NitroStack MCP server. + +## Defining Tools with `@Tool` +An MCP tool exposes a function that an AI client can invoke. Decorate a service or controller method with `@Tool`. + +### Key Tool Options: +* `name`: Kebab-case or snake_case unique identifier. +* `description`: Detailed description explaining when and how the client should use it. +* `inputSchema`: A Zod object schema for strict validation of inputs. +* `outputSchema` (optional): Zod schema validating the output structure. + +```typescript +import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core'; + +@Controller('weather') +export class WeatherService { + @Tool({ + name: 'get_current_weather', + description: 'Get the current weather forecast for a specific city.', + inputSchema: z.object({ + city: z.string().describe('The name of the city, e.g., San Francisco'), + unit: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }) + @InitialTool() // Auto-invoked when the AI client initializes/starts + async getWeather( + input: { city: string; unit: 'celsius' | 'fahrenheit' }, + ctx: ExecutionContext + ) { + ctx.logger.info(`Fetching weather for ${input.city}`); + // implementation + return { + city: input.city, + temp: 22, + condition: 'Sunny', + }; + } +} +``` + +## Defining Resources with `@Resource` +An MCP resource exposes static or dynamic data files/URIs that the AI client can read. + +### Key Resource Options: +* `uri`: URI pattern (e.g., `git://{owner}/{repo}/file` or static `app://config`). +* `name`: Unique name of the resource. +* `description`: Explanation of what data this resource provides. +* `mimeType`: Mime type of the response (e.g., `text/plain`, `application/json`). + +```typescript +import { Resource, ExecutionContext } from '@nitrostack/core'; + +export class ConfigResources { + @Resource({ + uri: 'app://settings', + name: 'Application Settings', + description: 'System-wide configuration settings and parameters.', + mimeType: 'application/json', + }) + async getSettings(ctx: ExecutionContext) { + return { + environment: 'development', + debugMode: true, + }; + } +} +``` + +## Defining Prompts with `@Prompt` +An MCP prompt exposes reusable templates or instruction sets that guide LLMs. + +### Key Prompt Options: +* `name`: Name of the prompt. +* `description`: Describes what task this prompt helps accomplish. +* `arguments`: Declares parameters the client can supply to customize the prompt template. + +```typescript +import { Prompt, ExecutionContext } from '@nitrostack/core'; + +export class PromptTemplates { + @Prompt({ + name: 'code_review', + description: 'Provide an intensive code review for a given code snippet.', + arguments: [ + { name: 'language', description: 'The programming language, e.g., TypeScript', required: true }, + { name: 'code', description: 'The code snippet to review', required: true }, + ], + }) + async getCodeReviewPrompt( + args: { language: string; code: string }, + ctx: ExecutionContext + ) { + return { + messages: [ + { + role: 'user', + content: `You are an expert software engineer. Review this ${args.language} code:\n\n${args.code}`, + }, + ], + }; + } +} +``` + +--- + +## Tool Policies: Caching (`@Cache`) and Rate Limiting (`@RateLimit`) +You can control tool execution behaviors (such as performance optimization and throttling) using method decorators. + +### 1. Caching with `@Cache` +Use `@Cache` to cache tool execution outputs for a specified duration (TTL in seconds). This reduces database or API overhead for frequent identical requests. + +#### Options: +* `ttl`: Cache time-to-live in seconds (required). +* `key` (optional): Custom function `(input: any, context?: any) => string` that returns a unique cache key based on inputs. If not defined, a key is auto-generated from serialized input arguments. + +#### Example: +```typescript +import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core'; + +export class StationTools { + @Tool({ + name: 'get_system_status', + description: 'Fetch real-time station metrics. Response is cached.', + inputSchema: z.object({}), + }) + @Cache({ ttl: 60 }) // Caches status for 60 seconds + async getSystemStatus() { + return { temperature: 21.5, oxygen: 0.98 }; + } + + @Tool({ + name: 'get_crew_status', + description: 'Fetch status of a crew member. Cached by crew ID.', + inputSchema: z.object({ id: z.string() }), + }) + @Cache({ + ttl: 300, + key: (input) => `crew:status:${input.id}` + }) + async getCrewStatus(input: { id: string }) { + // ... + } +} +``` + +### 2. Rate Limiting with `@RateLimit` +Use `@RateLimit` to restrict the number of tool invocations within a specified time window to prevent client abuse. + +#### Options: +* `requests`: Number of allowed requests in the window (required). +* `window`: Throttling duration window (required). Supports formats like `'1s'`, `'1m'`, `'1h'`. +* `key` (optional): Custom function `(context: ExecutionContext) => string` to group rate limits. Useful for rate-limiting per user role or API key. + +#### Example: +```typescript +import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core'; + +export class DiagnosticTools { + @Tool({ + name: 'run_deep_diagnostic', + description: 'Run intensive diagnostics. Rate limited.', + inputSchema: z.object({}), + }) + @RateLimit({ requests: 3, window: '1m' }) // Max 3 requests per minute globally + async runDeepDiagnostic() { + return { diagnosticReport: 'All systems operational.' }; + } + + @Tool({ + name: 'request_supply_drop', + description: 'Request inventory supplies. Rate limited per user.', + inputSchema: z.object({ item: z.string() }), + }) + @RateLimit({ + requests: 5, + window: '1h', + key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous' + }) + async requestSupply(input: { item: string }, ctx: ExecutionContext) { + // ... + } +} +``` + +--- + +## Handling File Uploads in Tools +NitroStack supports file uploads from MCP clients (like NitroStudio) by passing the file as a base64-encoded string inside a tool's input parameters. + +### 1. Declaring Input Schema for File Uploads +To accept an uploaded file, define three Zod fields in your tool's `inputSchema`: +* `file_name`: The name of the file (e.g. `report.csv`). +* `file_type`: The MIME type (e.g. `text/csv`). +* `file_content`: The base64-encoded string containing the file data. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; + +export class FileTools { + @Tool({ + name: 'upload_document', + description: 'Upload a text document or image.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async uploadDocument(input: any, ctx: ExecutionContext) { + // Processing logic + } +} +``` + +### 2. Decoding Base64 Payloads +File uploads can arrive in two formats depending on the client: +1. **Data URL format**: `data:image/png;base64,iVBORw0KGgo...` +2. **Raw Base64 format**: `iVBORw0KGgo...` + +Use the following universal decoder pattern to parse either format into a Node `Buffer`: + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; + +function decodeBase64File(content: string): Buffer { + const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (matches && matches.length === 3) { + // Data URL format - decode matches[2] + return Buffer.from(matches[2], 'base64'); + } else { + // Raw base64 format - decode input directly + return Buffer.from(content, 'base64'); + } +} +``` + +### 3. Secure File Saving Example +Always validate the directory paths to prevent directory traversal attacks when saving files to disk. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); + +export class SecureUploadTools { + @Tool({ + name: 'save_uploaded_file', + description: 'Decodes and saves an uploaded file securely.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async saveFile(input: any, ctx: ExecutionContext) { + // Ensure uploads directory exists + if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + } + + // Secure destination path to prevent path traversal + const safeName = path.basename(input.file_name); + const filePath = path.join(UPLOAD_DIR, safeName); + if (!filePath.startsWith(UPLOAD_DIR)) { + throw new Error('Invalid file path detected (path traversal).'); + } + + // Decode and write to disk + const buffer = decodeBase64File(input.file_content); + fs.writeFileSync(filePath, buffer); + + ctx.logger.info(`Successfully saved file: ${safeName}`); + return { success: true, path: filePath }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/ui-widgets/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.agents/skills/ui-widgets/SKILL.md @@ -0,0 +1,251 @@ +--- +name: nitrostack-ui-widgets +description: Best practices for linking tools to interactive frontend widgets using @Widget and @nitrostack/widgets SDK (including state sync, tool calling, display modes, media queries, and chat actions). +--- + +## When to Use +Use this skill when designing, building, or modifying interactive user interface widgets that display custom React content inside AI clients or NitroStudio. + +--- + +## 1. Backend Definition (`@Widget`) +To display a React-based widget for a tool's output, decorate the tool method with `@Widget`. + +### Options: +* **String Route**: A simple string representing the route identifier in the frontend React app (e.g. `'product-card'`). +* **Object Route**: Object including: + * `route` (required): The route path. + * `domain` (optional): Allowed sandbox domain. + * `csp` (optional): Content Security Policy guidelines. + +### Example: +```typescript +import { Tool, Widget, z } from '@nitrostack/core'; + +export class CatalogTools { + @Tool({ + name: 'fetch_product', + description: 'Get product information by barcode.', + inputSchema: z.object({ barcode: z.string() }), + }) + @Widget('product-details') // Maps to the "product-details" frontend component + async fetchProduct(input: { barcode: string }) { + return { + name: 'Super Nitro Energy Drink', + price: 2.99, + sku: input.barcode, + }; + } +} +``` + +--- + +## 2. Frontend React Widget (`@nitrostack/widgets`) +In your React widget frontend application (typically a Next.js client component), use the `useWidgetSDK` hook to receive input data from the client host. + +### React Component Example: +```tsx +'use client'; + +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +interface ProductData { + name: string; + price: number; + sku: string; +} + +export default function ProductDetailsWidget() { + const { isReady, getToolOutput, theme } = useWidgetSDK(); + const data = getToolOutput(); + + if (!isReady) { + return
Connecting to host...
; + } + + if (!data) { + return
No product data received.
; + } + + return ( +
+

{data.name}

+

${data.price.toFixed(2)}

+ SKU: {data.sku} +
+ ); +} +``` + +--- + +## 3. State Management & Synchronization (`useWidgetState`) +Use `useWidgetState` to manage and persist client-side widget state (e.g. selected tabs, filter values, input states). This state automatically synchronizes with the host application context, persisting it across page re-renders. + +### Example: +```tsx +import React from 'react'; +import { useWidgetState } from '@nitrostack/widgets'; + +export default function StationPanelWidget() { + const [state, setState] = useWidgetState(() => ({ + selectedTab: 'overview', + showExtendedInfo: false, + })); + + return ( +
+ +

Current Tab: {state?.selectedTab}

+
+ ); +} +``` + +--- + +## 4. Calling Core Tools from Widgets (`callTool`) +You can invoke other backend MCP tools directly from the frontend widget using `callTool`. This is useful for tool chaining or triggering detailed audits. + +### Example: +```tsx +import React, { useState } from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function SystemDiagnostics() { + const { callTool, isReady } = useWidgetSDK(); + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState(null); + + const runDiagnostic = async () => { + if (!isReady) return; + setIsRunning(true); + try { + const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' }); + setResult(response.result as string); + } catch (err) { + setResult('Diagnostic execution failed.'); + } finally { + setIsRunning(false); + } + }; + + return ( + + ); +} +``` + +--- + +## 5. Layout & Display Controls +Widgets can dynamically request size mode changes (fullscreen, inline, picture-in-picture) and adapt layouts to safe areas (dynamic islands/notches) or maximum height constraints. + +### Key Methods: +* `requestFullscreen()`: Switch host widget display to fullscreen. +* `requestInline()`: Switch host widget display back to inline. +* `requestPip()`: Float widget in Picture-in-Picture. +* `requestClose()`: Dismiss the widget completely. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function StatusBoard() { + const { + requestFullscreen, + requestInline, + requestClose, + displayMode, // Reactive property ('fullscreen' | 'inline' | 'pip') + maxHeight, // Reactive maxHeight constraint (in pixels) + getSafeArea // Insets data: { top, right, bottom, left } + } = useWidgetSDK(); + + const safeArea = getSafeArea() || { top: 0, bottom: 0 }; + + return ( +
+

Mode: {displayMode}

+ + + +
+ ); +} +``` + +--- + +## 6. Chat Navigation & Actions +Widgets can interact with the host chat pane using external browser links and follow-up prompts. + +### Key Methods: +* `openExternal(url)`: Open the target URL safely in the user's primary external browser. +* `sendFollowUpMessage(prompt)`: Insert a message into the chat flow, automatically submitting it to the LLM agent. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function MissionControl() { + const { openExternal, sendFollowUpMessage } = useWidgetSDK(); + + return ( +
+ {/* Open external documentation */} + + + {/* Ask LLM agent directly from the widget */} + +
+ ); +} +``` + +--- + +## 7. Media & Accessibility Queries +The SDK provides helper utilities to query target client capabilities for styling or accessibility. + +### Key Utilities: +* `prefersReducedMotion()`: Returns `true` if client settings specify reduced motion. Disable animations. +* `isPrimarilyTouchDevice()`: Returns `true` if the device has a coarse pointer (e.g. touch/mobile). Increase button target sizes. +* `isHoverAvailable()`: Returns `true` if pointer supports hover states. +* `prefersDarkColorScheme()`: Returns `true` if the system theme is dark. + +### Example: +```tsx +import React from 'react'; +import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets'; + +export default function AccessiblePanel() { + const isTouch = isPrimarilyTouchDevice(); + const reducedMotion = prefersReducedMotion(); + + return ( +
+ +
+ ); +} +``` + +--- + +## 8. Testing Widgets +* Open your project in **NitroStudio** for visual preview. +* Invoke the tool from the AI chat or testing pane to verify the widget updates instantly with the returned JSON structure. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/auth-security/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/auth-security/SKILL.md @@ -0,0 +1,141 @@ +--- +name: nitrostack-auth-security +description: Best practices for implementing JWT, API Keys, OAuth 2.1, and RBAC in a NitroStack application. +--- + +## When to Use +Use this skill when configuring security modules, implementing user authentication, restricting tool access via guards, or handling sensitive tokens. + +--- + +## 1. JSON Web Tokens (JWT) +To secure tools with JWT authentication: + +### Register `JWTModule`: +```typescript +import { JWTModule, Module, McpApp } from '@nitrostack/core'; + +@McpApp({ + server: { name: 'my-server', version: '1.0.0' } +}) +@Module({ + imports: [ + JWTModule.forRoot({ + secret: process.env.JWT_SECRET!, + expiresIn: '7d', + }), + ] +}) +export class AppModule {} +``` + +### Write a `JWTGuard`: +```typescript +import { Guard, ExecutionContext, Injectable, ConfigService } from '@nitrostack/core'; +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JWTGuard implements Guard { + constructor(private config: ConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const token = this.extractToken(context); + if (!token) return false; + + try { + const secret = this.config.get('JWT_SECRET'); + const payload = jwt.verify(token, secret) as any; + context.auth = { + subject: payload.sub, + role: payload.role, + token, + }; + return true; + } catch { + return false; + } + } + + private extractToken(context: ExecutionContext): string | null { + const auth = context.metadata?.authorization; + if (auth?.startsWith('Bearer ')) { + return auth.substring(7); + } + return null; + } +} +``` + +--- + +## 2. API Key Authentication +Use `ApiKeyModule` for service-to-service validation. + +### Register `ApiKeyModule`: +```typescript +import { ApiKeyModule, Module } from '@nitrostack/core'; + +@Module({ + imports: [ + ApiKeyModule.forRoot({ + keysEnvPrefix: 'API_KEY', // Reads API_KEY_1, API_KEY_2, etc. + headerName: 'x-api-key', + hashed: false, + }), + ] +}) +export class AppModule {} +``` + +### API Key Guard: +```typescript +import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core'; + +export class ApiKeyGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const apiKey = context.metadata?.['x-api-key'] || context.metadata?.apiKey; + if (!apiKey) return false; + + const isValid = await ApiKeyModule.validate(apiKey as string); + if (isValid) { + context.auth = { + subject: `apikey_${(apiKey as string).substring(0, 10)}`, + scopes: ['*'], + }; + return true; + } + return false; + } +} +``` + +--- + +## 3. Role-Based Access Control (RBAC) +Chain guards sequentially to implement user-role authorization. + +```typescript +import { Injectable, Guard, ExecutionContext, UseGuards, Tool, z } from '@nitrostack/core'; +import { JWTGuard } from './jwt.guard.js'; + +@Injectable() +export class AdminGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + // Requires JWTGuard to have populated context.auth first + return context.auth?.role === 'admin'; + } +} + +// Applying chained guards to a tool +export class SystemTools { + @Tool({ + name: 'reset_database', + description: 'Dangerous action: wipes database. Admin only.', + inputSchema: z.object({}), + }) + @UseGuards(JWTGuard, AdminGuard) // Chain auth first, then role check + async resetDatabase() { + return { success: true }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/mcp-app-architecture/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/mcp-app-architecture/SKILL.md @@ -0,0 +1,176 @@ +--- +name: nitrostack-mcp-app-architecture +description: Best practices and guidelines for bootstrapping, defining modules, using dependency injection, managing server lifecycles, and handling events in the NitroStack SDK. +--- + +## When to Use +Use this skill whenever you are bootstrapping a new NitroStack MCP server, creating modules, injecting services, or handling application lifecycle events. + +## Bootstrapping a NitroStack App +A NitroStack application is initialized with the `@McpApp` decorator on a root class, accompanied by a root `@Module`. + +```typescript +import { McpApp, Module } from '@nitrostack/core'; +import { DatabaseModule } from './database/database.module.js'; +import { UsersModule } from './users/users.module.js'; + +@McpApp({ + module: AppModule, + server: { + name: 'user-management-server', + version: '1.0.0', + }, +}) +@Module({ + imports: [DatabaseModule, UsersModule], +}) +export class AppModule {} +``` + +## Modules +Modules organize your application structure. Use the `@Module` decorator to define imports, exports, and providers. + +* **`imports`**: Other modules whose exported providers should be available in this module. +* **`providers`**: Services, tools, resources, or prompts that should be instantiated and managed by the DI container within this module. +* **`exports`**: Providers defined in this module that should be visible to other modules importing this one. + +```typescript +import { Module } from '@nitrostack/core'; +import { UsersService } from './users.service.js'; +import { UsersTools } from './users.tools.js'; + +@Module({ + providers: [UsersService, UsersTools], + exports: [UsersService], +}) + +## Controllers +Use the `@ControllerDecorator` (or alias it as `@Controller`) to group tools, resources, and prompts together. Controllers are automatically registered as singletons in the DI container. + +### Key Controller Options: +* **`prefix`**: A string prefix applied to every `@Tool` defined in this controller. For example, `@ControllerDecorator('github')` prefixing a tool named `create_issue` exposes it to MCP clients as `github_create_issue`. + +```typescript +import { ControllerDecorator as Controller, Tool, ExecutionContext } from '@nitrostack/core'; + +@Controller('github') +export class GitHubController { + @Tool({ + name: 'create_issue', + description: 'Create an issue in a repository', + inputSchema: z.object({ /* ... */ }) + }) + async createIssue(input: any, ctx: ExecutionContext) { + // Exposed to clients as "github_create_issue" + } +} +``` + +## Dependency Injection (DI) +NitroStack uses a robust dependency injection container to manage class instances and lifecycles. + +### Injection Lifecycles +1. **Singleton (Default)**: A single instance is shared across the entire application. +2. **Transient**: A new instance is created every time it is resolved/injected. +3. **Scoped**: A new instance is created per incoming request or context. + +```typescript +import { Injectable, Scope } from '@nitrostack/core'; + +@Injectable({ scope: Scope.SINGLETON }) +export class UsersService { + constructor(private readonly db: DatabaseService) {} + + async getUser(id: string) { + return this.db.query('SELECT * FROM users WHERE id = $1', [id]); + } +} +``` + +## Lifecycles and Hooks +Implement NestJS-style lifecycle interfaces on modules, controllers, or providers to hook into application state changes: + +* **`OnModuleInit`** (`onModuleInit`): Called after modules have initialized but before the server starts listening. +* **`OnApplicationBootstrap`** (`onApplicationBootstrap`): Called once the server is fully started and listening. +* **`OnModuleDestroy`** (`onModuleDestroy`): Called when the module or application is shutting down. +* **`BeforeApplicationShutdown`** (`beforeApplicationShutdown(signal?: string)`): Called before the application starts shutting down. Receives the OS signal (e.g. `SIGINT`). +* **`OnApplicationShutdown`** (`onApplicationShutdown(signal?: string)`): Called during shutdown. Receives the OS signal. + +```typescript +import { + Injectable, + OnModuleInit, + OnApplicationBootstrap, + OnModuleDestroy, + BeforeApplicationShutdown, + OnApplicationShutdown +} from '@nitrostack/core'; + +@Injectable() +export class DatabaseService + implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown +{ + async onModuleInit() { + await this.connect(); + } + + async onApplicationBootstrap() { + console.log('App ready to handle connections.'); + } + + async onModuleDestroy() { + await this.cleanupPendingQueries(); + } + + async beforeApplicationShutdown(signal?: string) { + console.log(`Shutting down soon (signal: ${signal}).`); + } + + async onApplicationShutdown(signal?: string) { + await this.disconnect(); + } +} +``` + +--- + +## Eventing System (`emitEvent` and `@OnEvent`) +NitroStack includes an internal eventing system to decouple components. A service or tool can emit an event using `emitEvent`, and any injectable class (like a handler service or controller) can subscribe using the `@OnEvent` decorator. + +### 1. Emitting Events +Call `emitEvent` to dispatch an event payload asynchronously. + +```typescript +import { Injectable, emitEvent } from '@nitrostack/core'; + +@Injectable() +export class SpaceShipService { + async launchShip(shipId: string) { + // Process launch... + + // Dispatch event + emitEvent('ship.launched', { + shipId, + timestamp: new Date().toISOString(), + }); + } +} +``` + +### 2. Listening to Events +Decorate a method inside any `@Injectable()` class with `@OnEvent('event_pattern')` to register it as an event handler. + +```typescript +import { Injectable, OnEvent } from '@nitrostack/core'; + +@Injectable({ deps: [] }) +export class FlightLogHandler { + @OnEvent('ship.launched') + async logLaunch(data: { shipId: string; timestamp: string }) { + console.error(`🚀 [EVENT] Ship ${data.shipId} was successfully launched at ${data.timestamp}`); + } +} +``` + +> [!NOTE] +> For the `@OnEvent` decorator to register properly, the containing class must be declared as a provider inside an active module. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/middleware-pipeline/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/middleware-pipeline/SKILL.md @@ -0,0 +1,235 @@ +--- +name: nitrostack-middleware-pipeline +description: Best practices for implementing and applying Guards, Interceptors, Middleware, Pipes, and Exception Filters in the NitroStack SDK. +--- + +## When to Use +Use this skill when implementing request validation, authorization checks, response mapping, logging, error handling, or performance tracking on NitroStack tool methods. + +--- + +## 1. Guards (`Guard` and `@UseGuards`) +Guards determine if a request should be processed by a tool handler based on authentication, authorization, or other conditions. + +### Interface: +```typescript +import { Guard, ExecutionContext } from '@nitrostack/core'; + +export interface Guard { + canActivate(context: ExecutionContext): boolean | Promise; +} +``` + +### Example: +```typescript +import { Guard, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class RolesGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const userRoles = context.clientMetadata?.roles || []; + return userRoles.includes('admin'); + } +} +``` + +Apply the guard using `@UseGuards(...)`: +```typescript +import { Tool, UseGuards, z } from '@nitrostack/core'; +import { RolesGuard } from './roles.guard.js'; + +export class AdminTools { + @Tool({ + name: 'delete_system_logs', + description: 'Delete all system logs from the server.', + inputSchema: z.object({}), + }) + @UseGuards(RolesGuard) + async deleteLogs() { + return { success: true }; + } +} +``` + +--- + +## 2. Interceptors (`InterceptorInterface` and `@UseInterceptors`) +Interceptors can transform/intercept input arguments or mapped output from a tool method execution. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface InterceptorInterface { + intercept(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { InterceptorInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class TimingInterceptor implements InterceptorInterface { + async intercept(context: ExecutionContext, next: () => Promise): Promise { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + context.logger.info(`Execution took ${duration}ms`); + return { + ...result, + _meta: { durationMs: duration } + }; + } +} +``` + +--- + +## 3. Exception Filters (`ExceptionFilterInterface` and `@UseFilters`) +Exception filters catch any errors thrown within guards, interceptors, or the tool handlers themselves, mapping them into user-friendly JSON payloads. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext): unknown | Promise; +} +``` + +### Example: +```typescript +import { ExceptionFilterInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class CustomExceptionFilter implements ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext) { + const message = exception instanceof Error ? exception.message : 'Unknown error'; + return { + error: true, + message, + timestamp: new Date().toISOString() + }; + } +} +``` + +Apply the filter using `@UseFilters(...)` on a tool method: + +```typescript +import { Tool, UseFilters, z } from '@nitrostack/core'; +import { CustomExceptionFilter } from './custom-exception.filter.js'; + +export class LoggingTools { + @Tool({ + name: 'generate_report', + description: 'Generates system usage reports.', + inputSchema: z.object({}), + }) + @UseFilters(CustomExceptionFilter) + async generateReport() { + throw new Error('Report generation is not implemented yet.'); + } +} +``` + +--- + +## 4. Middleware (`MiddlewareInterface`, `@Middleware` and `@UseMiddleware`) +Middleware executes before the request reaches the tool handler, and can wrap the handler execution by invoking `next()`. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface MiddlewareInterface { + use(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { Middleware, MiddlewareInterface, ExecutionContext } from '@nitrostack/core'; + +@Middleware() +export class LoggingMiddleware implements MiddlewareInterface { + async use(context: ExecutionContext, next: () => Promise): Promise { + context.logger.info(`Entering tool: ${context.toolName}`); + try { + const result = await next(); + context.logger.info(`Exiting tool: ${context.toolName}`); + return result; + } catch (error) { + context.logger.error(`Error in tool: ${error}`); + throw error; + } + } +} +``` + +Apply the middleware using `@UseMiddleware(...)` on a tool method: +```typescript +import { Tool, UseMiddleware, z } from '@nitrostack/core'; +import { LoggingMiddleware } from './logging.middleware.js'; + +export class StationTools { + @Tool({ + name: 'fetch_logs', + description: 'Fetch station operations logs.', + inputSchema: z.object({}), + }) + @UseMiddleware(LoggingMiddleware) + async fetchLogs() { + return { status: 'operational' }; + } +} +``` + +--- + +## 5. Pipes (`PipeInterface`, `@Pipe` and `@UsePipes`) +Pipes are used to transform or validate input arguments before they reach the tool handler method. + +### Interface: +```typescript +import { ArgumentMetadata } from '@nitrostack/core'; + +export interface PipeInterface { + transform(value: T, metadata: ArgumentMetadata): R | Promise; +} +``` + +### Example: +```typescript +import { Pipe, PipeInterface, ArgumentMetadata } from '@nitrostack/core'; + +@Pipe() +export class TrimPipe implements PipeInterface, Record> { + transform(value: Record, metadata: ArgumentMetadata) { + const trimmed: Record = {}; + for (const [key, val] of Object.entries(value)) { + trimmed[key] = typeof val === 'string' ? val.trim() : val; + } + return trimmed; + } +} +``` + +Apply the pipe using `@UsePipes(...)` on a tool method: +```typescript +import { Tool, UsePipes, z } from '@nitrostack/core'; +import { TrimPipe } from './trim.pipe.js'; + +export class MessagingTools { + @Tool({ + name: 'send_message', + description: 'Send a message to other stations.', + inputSchema: z.object({ text: z.string() }), + }) + @UsePipes(TrimPipe) + async sendMessage(input: { text: string }) { + return { sentText: input.text }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/tools-resources-prompts/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/tools-resources-prompts/SKILL.md @@ -0,0 +1,287 @@ +--- +name: nitrostack-tools-resources-prompts +description: Guidelines and patterns for defining Tools, Resources, and Prompts in a NitroStack application with schema validation via Zod, including caching, rate-limiting, and base64 file uploads. +--- + +## When to Use +Use this skill whenever you are defining, editing, or validating tools, resources, or prompts on a NitroStack MCP server. + +## Defining Tools with `@Tool` +An MCP tool exposes a function that an AI client can invoke. Decorate a service or controller method with `@Tool`. + +### Key Tool Options: +* `name`: Kebab-case or snake_case unique identifier. +* `description`: Detailed description explaining when and how the client should use it. +* `inputSchema`: A Zod object schema for strict validation of inputs. +* `outputSchema` (optional): Zod schema validating the output structure. + +```typescript +import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core'; + +@Controller('weather') +export class WeatherService { + @Tool({ + name: 'get_current_weather', + description: 'Get the current weather forecast for a specific city.', + inputSchema: z.object({ + city: z.string().describe('The name of the city, e.g., San Francisco'), + unit: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }) + @InitialTool() // Auto-invoked when the AI client initializes/starts + async getWeather( + input: { city: string; unit: 'celsius' | 'fahrenheit' }, + ctx: ExecutionContext + ) { + ctx.logger.info(`Fetching weather for ${input.city}`); + // implementation + return { + city: input.city, + temp: 22, + condition: 'Sunny', + }; + } +} +``` + +## Defining Resources with `@Resource` +An MCP resource exposes static or dynamic data files/URIs that the AI client can read. + +### Key Resource Options: +* `uri`: URI pattern (e.g., `git://{owner}/{repo}/file` or static `app://config`). +* `name`: Unique name of the resource. +* `description`: Explanation of what data this resource provides. +* `mimeType`: Mime type of the response (e.g., `text/plain`, `application/json`). + +```typescript +import { Resource, ExecutionContext } from '@nitrostack/core'; + +export class ConfigResources { + @Resource({ + uri: 'app://settings', + name: 'Application Settings', + description: 'System-wide configuration settings and parameters.', + mimeType: 'application/json', + }) + async getSettings(ctx: ExecutionContext) { + return { + environment: 'development', + debugMode: true, + }; + } +} +``` + +## Defining Prompts with `@Prompt` +An MCP prompt exposes reusable templates or instruction sets that guide LLMs. + +### Key Prompt Options: +* `name`: Name of the prompt. +* `description`: Describes what task this prompt helps accomplish. +* `arguments`: Declares parameters the client can supply to customize the prompt template. + +```typescript +import { Prompt, ExecutionContext } from '@nitrostack/core'; + +export class PromptTemplates { + @Prompt({ + name: 'code_review', + description: 'Provide an intensive code review for a given code snippet.', + arguments: [ + { name: 'language', description: 'The programming language, e.g., TypeScript', required: true }, + { name: 'code', description: 'The code snippet to review', required: true }, + ], + }) + async getCodeReviewPrompt( + args: { language: string; code: string }, + ctx: ExecutionContext + ) { + return { + messages: [ + { + role: 'user', + content: `You are an expert software engineer. Review this ${args.language} code:\n\n${args.code}`, + }, + ], + }; + } +} +``` + +--- + +## Tool Policies: Caching (`@Cache`) and Rate Limiting (`@RateLimit`) +You can control tool execution behaviors (such as performance optimization and throttling) using method decorators. + +### 1. Caching with `@Cache` +Use `@Cache` to cache tool execution outputs for a specified duration (TTL in seconds). This reduces database or API overhead for frequent identical requests. + +#### Options: +* `ttl`: Cache time-to-live in seconds (required). +* `key` (optional): Custom function `(input: any, context?: any) => string` that returns a unique cache key based on inputs. If not defined, a key is auto-generated from serialized input arguments. + +#### Example: +```typescript +import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core'; + +export class StationTools { + @Tool({ + name: 'get_system_status', + description: 'Fetch real-time station metrics. Response is cached.', + inputSchema: z.object({}), + }) + @Cache({ ttl: 60 }) // Caches status for 60 seconds + async getSystemStatus() { + return { temperature: 21.5, oxygen: 0.98 }; + } + + @Tool({ + name: 'get_crew_status', + description: 'Fetch status of a crew member. Cached by crew ID.', + inputSchema: z.object({ id: z.string() }), + }) + @Cache({ + ttl: 300, + key: (input) => `crew:status:${input.id}` + }) + async getCrewStatus(input: { id: string }) { + // ... + } +} +``` + +### 2. Rate Limiting with `@RateLimit` +Use `@RateLimit` to restrict the number of tool invocations within a specified time window to prevent client abuse. + +#### Options: +* `requests`: Number of allowed requests in the window (required). +* `window`: Throttling duration window (required). Supports formats like `'1s'`, `'1m'`, `'1h'`. +* `key` (optional): Custom function `(context: ExecutionContext) => string` to group rate limits. Useful for rate-limiting per user role or API key. + +#### Example: +```typescript +import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core'; + +export class DiagnosticTools { + @Tool({ + name: 'run_deep_diagnostic', + description: 'Run intensive diagnostics. Rate limited.', + inputSchema: z.object({}), + }) + @RateLimit({ requests: 3, window: '1m' }) // Max 3 requests per minute globally + async runDeepDiagnostic() { + return { diagnosticReport: 'All systems operational.' }; + } + + @Tool({ + name: 'request_supply_drop', + description: 'Request inventory supplies. Rate limited per user.', + inputSchema: z.object({ item: z.string() }), + }) + @RateLimit({ + requests: 5, + window: '1h', + key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous' + }) + async requestSupply(input: { item: string }, ctx: ExecutionContext) { + // ... + } +} +``` + +--- + +## Handling File Uploads in Tools +NitroStack supports file uploads from MCP clients (like NitroStudio) by passing the file as a base64-encoded string inside a tool's input parameters. + +### 1. Declaring Input Schema for File Uploads +To accept an uploaded file, define three Zod fields in your tool's `inputSchema`: +* `file_name`: The name of the file (e.g. `report.csv`). +* `file_type`: The MIME type (e.g. `text/csv`). +* `file_content`: The base64-encoded string containing the file data. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; + +export class FileTools { + @Tool({ + name: 'upload_document', + description: 'Upload a text document or image.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async uploadDocument(input: any, ctx: ExecutionContext) { + // Processing logic + } +} +``` + +### 2. Decoding Base64 Payloads +File uploads can arrive in two formats depending on the client: +1. **Data URL format**: `data:image/png;base64,iVBORw0KGgo...` +2. **Raw Base64 format**: `iVBORw0KGgo...` + +Use the following universal decoder pattern to parse either format into a Node `Buffer`: + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; + +function decodeBase64File(content: string): Buffer { + const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (matches && matches.length === 3) { + // Data URL format - decode matches[2] + return Buffer.from(matches[2], 'base64'); + } else { + // Raw base64 format - decode input directly + return Buffer.from(content, 'base64'); + } +} +``` + +### 3. Secure File Saving Example +Always validate the directory paths to prevent directory traversal attacks when saving files to disk. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); + +export class SecureUploadTools { + @Tool({ + name: 'save_uploaded_file', + description: 'Decodes and saves an uploaded file securely.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async saveFile(input: any, ctx: ExecutionContext) { + // Ensure uploads directory exists + if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + } + + // Secure destination path to prevent path traversal + const safeName = path.basename(input.file_name); + const filePath = path.join(UPLOAD_DIR, safeName); + if (!filePath.startsWith(UPLOAD_DIR)) { + throw new Error('Invalid file path detected (path traversal).'); + } + + // Decode and write to disk + const buffer = decodeBase64File(input.file_content); + fs.writeFileSync(filePath, buffer); + + ctx.logger.info(`Successfully saved file: ${safeName}`); + return { success: true, path: filePath }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/ui-widgets/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.antigravity/skills/ui-widgets/SKILL.md @@ -0,0 +1,251 @@ +--- +name: nitrostack-ui-widgets +description: Best practices for linking tools to interactive frontend widgets using @Widget and @nitrostack/widgets SDK (including state sync, tool calling, display modes, media queries, and chat actions). +--- + +## When to Use +Use this skill when designing, building, or modifying interactive user interface widgets that display custom React content inside AI clients or NitroStudio. + +--- + +## 1. Backend Definition (`@Widget`) +To display a React-based widget for a tool's output, decorate the tool method with `@Widget`. + +### Options: +* **String Route**: A simple string representing the route identifier in the frontend React app (e.g. `'product-card'`). +* **Object Route**: Object including: + * `route` (required): The route path. + * `domain` (optional): Allowed sandbox domain. + * `csp` (optional): Content Security Policy guidelines. + +### Example: +```typescript +import { Tool, Widget, z } from '@nitrostack/core'; + +export class CatalogTools { + @Tool({ + name: 'fetch_product', + description: 'Get product information by barcode.', + inputSchema: z.object({ barcode: z.string() }), + }) + @Widget('product-details') // Maps to the "product-details" frontend component + async fetchProduct(input: { barcode: string }) { + return { + name: 'Super Nitro Energy Drink', + price: 2.99, + sku: input.barcode, + }; + } +} +``` + +--- + +## 2. Frontend React Widget (`@nitrostack/widgets`) +In your React widget frontend application (typically a Next.js client component), use the `useWidgetSDK` hook to receive input data from the client host. + +### React Component Example: +```tsx +'use client'; + +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +interface ProductData { + name: string; + price: number; + sku: string; +} + +export default function ProductDetailsWidget() { + const { isReady, getToolOutput, theme } = useWidgetSDK(); + const data = getToolOutput(); + + if (!isReady) { + return
Connecting to host...
; + } + + if (!data) { + return
No product data received.
; + } + + return ( +
+

{data.name}

+

${data.price.toFixed(2)}

+ SKU: {data.sku} +
+ ); +} +``` + +--- + +## 3. State Management & Synchronization (`useWidgetState`) +Use `useWidgetState` to manage and persist client-side widget state (e.g. selected tabs, filter values, input states). This state automatically synchronizes with the host application context, persisting it across page re-renders. + +### Example: +```tsx +import React from 'react'; +import { useWidgetState } from '@nitrostack/widgets'; + +export default function StationPanelWidget() { + const [state, setState] = useWidgetState(() => ({ + selectedTab: 'overview', + showExtendedInfo: false, + })); + + return ( +
+ +

Current Tab: {state?.selectedTab}

+
+ ); +} +``` + +--- + +## 4. Calling Core Tools from Widgets (`callTool`) +You can invoke other backend MCP tools directly from the frontend widget using `callTool`. This is useful for tool chaining or triggering detailed audits. + +### Example: +```tsx +import React, { useState } from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function SystemDiagnostics() { + const { callTool, isReady } = useWidgetSDK(); + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState(null); + + const runDiagnostic = async () => { + if (!isReady) return; + setIsRunning(true); + try { + const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' }); + setResult(response.result as string); + } catch (err) { + setResult('Diagnostic execution failed.'); + } finally { + setIsRunning(false); + } + }; + + return ( + + ); +} +``` + +--- + +## 5. Layout & Display Controls +Widgets can dynamically request size mode changes (fullscreen, inline, picture-in-picture) and adapt layouts to safe areas (dynamic islands/notches) or maximum height constraints. + +### Key Methods: +* `requestFullscreen()`: Switch host widget display to fullscreen. +* `requestInline()`: Switch host widget display back to inline. +* `requestPip()`: Float widget in Picture-in-Picture. +* `requestClose()`: Dismiss the widget completely. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function StatusBoard() { + const { + requestFullscreen, + requestInline, + requestClose, + displayMode, // Reactive property ('fullscreen' | 'inline' | 'pip') + maxHeight, // Reactive maxHeight constraint (in pixels) + getSafeArea // Insets data: { top, right, bottom, left } + } = useWidgetSDK(); + + const safeArea = getSafeArea() || { top: 0, bottom: 0 }; + + return ( +
+

Mode: {displayMode}

+ + + +
+ ); +} +``` + +--- + +## 6. Chat Navigation & Actions +Widgets can interact with the host chat pane using external browser links and follow-up prompts. + +### Key Methods: +* `openExternal(url)`: Open the target URL safely in the user's primary external browser. +* `sendFollowUpMessage(prompt)`: Insert a message into the chat flow, automatically submitting it to the LLM agent. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function MissionControl() { + const { openExternal, sendFollowUpMessage } = useWidgetSDK(); + + return ( +
+ {/* Open external documentation */} + + + {/* Ask LLM agent directly from the widget */} + +
+ ); +} +``` + +--- + +## 7. Media & Accessibility Queries +The SDK provides helper utilities to query target client capabilities for styling or accessibility. + +### Key Utilities: +* `prefersReducedMotion()`: Returns `true` if client settings specify reduced motion. Disable animations. +* `isPrimarilyTouchDevice()`: Returns `true` if the device has a coarse pointer (e.g. touch/mobile). Increase button target sizes. +* `isHoverAvailable()`: Returns `true` if pointer supports hover states. +* `prefersDarkColorScheme()`: Returns `true` if the system theme is dark. + +### Example: +```tsx +import React from 'react'; +import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets'; + +export default function AccessiblePanel() { + const isTouch = isPrimarilyTouchDevice(); + const reducedMotion = prefersReducedMotion(); + + return ( +
+ +
+ ); +} +``` + +--- + +## 8. Testing Widgets +* Open your project in **NitroStudio** for visual preview. +* Invoke the tool from the AI chat or testing pane to verify the widget updates instantly with the returned JSON structure. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/auth-security/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/auth-security/SKILL.md @@ -0,0 +1,141 @@ +--- +name: nitrostack-auth-security +description: Best practices for implementing JWT, API Keys, OAuth 2.1, and RBAC in a NitroStack application. +--- + +## When to Use +Use this skill when configuring security modules, implementing user authentication, restricting tool access via guards, or handling sensitive tokens. + +--- + +## 1. JSON Web Tokens (JWT) +To secure tools with JWT authentication: + +### Register `JWTModule`: +```typescript +import { JWTModule, Module, McpApp } from '@nitrostack/core'; + +@McpApp({ + server: { name: 'my-server', version: '1.0.0' } +}) +@Module({ + imports: [ + JWTModule.forRoot({ + secret: process.env.JWT_SECRET!, + expiresIn: '7d', + }), + ] +}) +export class AppModule {} +``` + +### Write a `JWTGuard`: +```typescript +import { Guard, ExecutionContext, Injectable, ConfigService } from '@nitrostack/core'; +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JWTGuard implements Guard { + constructor(private config: ConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const token = this.extractToken(context); + if (!token) return false; + + try { + const secret = this.config.get('JWT_SECRET'); + const payload = jwt.verify(token, secret) as any; + context.auth = { + subject: payload.sub, + role: payload.role, + token, + }; + return true; + } catch { + return false; + } + } + + private extractToken(context: ExecutionContext): string | null { + const auth = context.metadata?.authorization; + if (auth?.startsWith('Bearer ')) { + return auth.substring(7); + } + return null; + } +} +``` + +--- + +## 2. API Key Authentication +Use `ApiKeyModule` for service-to-service validation. + +### Register `ApiKeyModule`: +```typescript +import { ApiKeyModule, Module } from '@nitrostack/core'; + +@Module({ + imports: [ + ApiKeyModule.forRoot({ + keysEnvPrefix: 'API_KEY', // Reads API_KEY_1, API_KEY_2, etc. + headerName: 'x-api-key', + hashed: false, + }), + ] +}) +export class AppModule {} +``` + +### API Key Guard: +```typescript +import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core'; + +export class ApiKeyGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const apiKey = context.metadata?.['x-api-key'] || context.metadata?.apiKey; + if (!apiKey) return false; + + const isValid = await ApiKeyModule.validate(apiKey as string); + if (isValid) { + context.auth = { + subject: `apikey_${(apiKey as string).substring(0, 10)}`, + scopes: ['*'], + }; + return true; + } + return false; + } +} +``` + +--- + +## 3. Role-Based Access Control (RBAC) +Chain guards sequentially to implement user-role authorization. + +```typescript +import { Injectable, Guard, ExecutionContext, UseGuards, Tool, z } from '@nitrostack/core'; +import { JWTGuard } from './jwt.guard.js'; + +@Injectable() +export class AdminGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + // Requires JWTGuard to have populated context.auth first + return context.auth?.role === 'admin'; + } +} + +// Applying chained guards to a tool +export class SystemTools { + @Tool({ + name: 'reset_database', + description: 'Dangerous action: wipes database. Admin only.', + inputSchema: z.object({}), + }) + @UseGuards(JWTGuard, AdminGuard) // Chain auth first, then role check + async resetDatabase() { + return { success: true }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/mcp-app-architecture/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/mcp-app-architecture/SKILL.md @@ -0,0 +1,176 @@ +--- +name: nitrostack-mcp-app-architecture +description: Best practices and guidelines for bootstrapping, defining modules, using dependency injection, managing server lifecycles, and handling events in the NitroStack SDK. +--- + +## When to Use +Use this skill whenever you are bootstrapping a new NitroStack MCP server, creating modules, injecting services, or handling application lifecycle events. + +## Bootstrapping a NitroStack App +A NitroStack application is initialized with the `@McpApp` decorator on a root class, accompanied by a root `@Module`. + +```typescript +import { McpApp, Module } from '@nitrostack/core'; +import { DatabaseModule } from './database/database.module.js'; +import { UsersModule } from './users/users.module.js'; + +@McpApp({ + module: AppModule, + server: { + name: 'user-management-server', + version: '1.0.0', + }, +}) +@Module({ + imports: [DatabaseModule, UsersModule], +}) +export class AppModule {} +``` + +## Modules +Modules organize your application structure. Use the `@Module` decorator to define imports, exports, and providers. + +* **`imports`**: Other modules whose exported providers should be available in this module. +* **`providers`**: Services, tools, resources, or prompts that should be instantiated and managed by the DI container within this module. +* **`exports`**: Providers defined in this module that should be visible to other modules importing this one. + +```typescript +import { Module } from '@nitrostack/core'; +import { UsersService } from './users.service.js'; +import { UsersTools } from './users.tools.js'; + +@Module({ + providers: [UsersService, UsersTools], + exports: [UsersService], +}) + +## Controllers +Use the `@ControllerDecorator` (or alias it as `@Controller`) to group tools, resources, and prompts together. Controllers are automatically registered as singletons in the DI container. + +### Key Controller Options: +* **`prefix`**: A string prefix applied to every `@Tool` defined in this controller. For example, `@ControllerDecorator('github')` prefixing a tool named `create_issue` exposes it to MCP clients as `github_create_issue`. + +```typescript +import { ControllerDecorator as Controller, Tool, ExecutionContext } from '@nitrostack/core'; + +@Controller('github') +export class GitHubController { + @Tool({ + name: 'create_issue', + description: 'Create an issue in a repository', + inputSchema: z.object({ /* ... */ }) + }) + async createIssue(input: any, ctx: ExecutionContext) { + // Exposed to clients as "github_create_issue" + } +} +``` + +## Dependency Injection (DI) +NitroStack uses a robust dependency injection container to manage class instances and lifecycles. + +### Injection Lifecycles +1. **Singleton (Default)**: A single instance is shared across the entire application. +2. **Transient**: A new instance is created every time it is resolved/injected. +3. **Scoped**: A new instance is created per incoming request or context. + +```typescript +import { Injectable, Scope } from '@nitrostack/core'; + +@Injectable({ scope: Scope.SINGLETON }) +export class UsersService { + constructor(private readonly db: DatabaseService) {} + + async getUser(id: string) { + return this.db.query('SELECT * FROM users WHERE id = $1', [id]); + } +} +``` + +## Lifecycles and Hooks +Implement NestJS-style lifecycle interfaces on modules, controllers, or providers to hook into application state changes: + +* **`OnModuleInit`** (`onModuleInit`): Called after modules have initialized but before the server starts listening. +* **`OnApplicationBootstrap`** (`onApplicationBootstrap`): Called once the server is fully started and listening. +* **`OnModuleDestroy`** (`onModuleDestroy`): Called when the module or application is shutting down. +* **`BeforeApplicationShutdown`** (`beforeApplicationShutdown(signal?: string)`): Called before the application starts shutting down. Receives the OS signal (e.g. `SIGINT`). +* **`OnApplicationShutdown`** (`onApplicationShutdown(signal?: string)`): Called during shutdown. Receives the OS signal. + +```typescript +import { + Injectable, + OnModuleInit, + OnApplicationBootstrap, + OnModuleDestroy, + BeforeApplicationShutdown, + OnApplicationShutdown +} from '@nitrostack/core'; + +@Injectable() +export class DatabaseService + implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown +{ + async onModuleInit() { + await this.connect(); + } + + async onApplicationBootstrap() { + console.log('App ready to handle connections.'); + } + + async onModuleDestroy() { + await this.cleanupPendingQueries(); + } + + async beforeApplicationShutdown(signal?: string) { + console.log(`Shutting down soon (signal: ${signal}).`); + } + + async onApplicationShutdown(signal?: string) { + await this.disconnect(); + } +} +``` + +--- + +## Eventing System (`emitEvent` and `@OnEvent`) +NitroStack includes an internal eventing system to decouple components. A service or tool can emit an event using `emitEvent`, and any injectable class (like a handler service or controller) can subscribe using the `@OnEvent` decorator. + +### 1. Emitting Events +Call `emitEvent` to dispatch an event payload asynchronously. + +```typescript +import { Injectable, emitEvent } from '@nitrostack/core'; + +@Injectable() +export class SpaceShipService { + async launchShip(shipId: string) { + // Process launch... + + // Dispatch event + emitEvent('ship.launched', { + shipId, + timestamp: new Date().toISOString(), + }); + } +} +``` + +### 2. Listening to Events +Decorate a method inside any `@Injectable()` class with `@OnEvent('event_pattern')` to register it as an event handler. + +```typescript +import { Injectable, OnEvent } from '@nitrostack/core'; + +@Injectable({ deps: [] }) +export class FlightLogHandler { + @OnEvent('ship.launched') + async logLaunch(data: { shipId: string; timestamp: string }) { + console.error(`🚀 [EVENT] Ship ${data.shipId} was successfully launched at ${data.timestamp}`); + } +} +``` + +> [!NOTE] +> For the `@OnEvent` decorator to register properly, the containing class must be declared as a provider inside an active module. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/middleware-pipeline/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/middleware-pipeline/SKILL.md @@ -0,0 +1,235 @@ +--- +name: nitrostack-middleware-pipeline +description: Best practices for implementing and applying Guards, Interceptors, Middleware, Pipes, and Exception Filters in the NitroStack SDK. +--- + +## When to Use +Use this skill when implementing request validation, authorization checks, response mapping, logging, error handling, or performance tracking on NitroStack tool methods. + +--- + +## 1. Guards (`Guard` and `@UseGuards`) +Guards determine if a request should be processed by a tool handler based on authentication, authorization, or other conditions. + +### Interface: +```typescript +import { Guard, ExecutionContext } from '@nitrostack/core'; + +export interface Guard { + canActivate(context: ExecutionContext): boolean | Promise; +} +``` + +### Example: +```typescript +import { Guard, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class RolesGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const userRoles = context.clientMetadata?.roles || []; + return userRoles.includes('admin'); + } +} +``` + +Apply the guard using `@UseGuards(...)`: +```typescript +import { Tool, UseGuards, z } from '@nitrostack/core'; +import { RolesGuard } from './roles.guard.js'; + +export class AdminTools { + @Tool({ + name: 'delete_system_logs', + description: 'Delete all system logs from the server.', + inputSchema: z.object({}), + }) + @UseGuards(RolesGuard) + async deleteLogs() { + return { success: true }; + } +} +``` + +--- + +## 2. Interceptors (`InterceptorInterface` and `@UseInterceptors`) +Interceptors can transform/intercept input arguments or mapped output from a tool method execution. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface InterceptorInterface { + intercept(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { InterceptorInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class TimingInterceptor implements InterceptorInterface { + async intercept(context: ExecutionContext, next: () => Promise): Promise { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + context.logger.info(`Execution took ${duration}ms`); + return { + ...result, + _meta: { durationMs: duration } + }; + } +} +``` + +--- + +## 3. Exception Filters (`ExceptionFilterInterface` and `@UseFilters`) +Exception filters catch any errors thrown within guards, interceptors, or the tool handlers themselves, mapping them into user-friendly JSON payloads. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext): unknown | Promise; +} +``` + +### Example: +```typescript +import { ExceptionFilterInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class CustomExceptionFilter implements ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext) { + const message = exception instanceof Error ? exception.message : 'Unknown error'; + return { + error: true, + message, + timestamp: new Date().toISOString() + }; + } +} +``` + +Apply the filter using `@UseFilters(...)` on a tool method: + +```typescript +import { Tool, UseFilters, z } from '@nitrostack/core'; +import { CustomExceptionFilter } from './custom-exception.filter.js'; + +export class LoggingTools { + @Tool({ + name: 'generate_report', + description: 'Generates system usage reports.', + inputSchema: z.object({}), + }) + @UseFilters(CustomExceptionFilter) + async generateReport() { + throw new Error('Report generation is not implemented yet.'); + } +} +``` + +--- + +## 4. Middleware (`MiddlewareInterface`, `@Middleware` and `@UseMiddleware`) +Middleware executes before the request reaches the tool handler, and can wrap the handler execution by invoking `next()`. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface MiddlewareInterface { + use(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { Middleware, MiddlewareInterface, ExecutionContext } from '@nitrostack/core'; + +@Middleware() +export class LoggingMiddleware implements MiddlewareInterface { + async use(context: ExecutionContext, next: () => Promise): Promise { + context.logger.info(`Entering tool: ${context.toolName}`); + try { + const result = await next(); + context.logger.info(`Exiting tool: ${context.toolName}`); + return result; + } catch (error) { + context.logger.error(`Error in tool: ${error}`); + throw error; + } + } +} +``` + +Apply the middleware using `@UseMiddleware(...)` on a tool method: +```typescript +import { Tool, UseMiddleware, z } from '@nitrostack/core'; +import { LoggingMiddleware } from './logging.middleware.js'; + +export class StationTools { + @Tool({ + name: 'fetch_logs', + description: 'Fetch station operations logs.', + inputSchema: z.object({}), + }) + @UseMiddleware(LoggingMiddleware) + async fetchLogs() { + return { status: 'operational' }; + } +} +``` + +--- + +## 5. Pipes (`PipeInterface`, `@Pipe` and `@UsePipes`) +Pipes are used to transform or validate input arguments before they reach the tool handler method. + +### Interface: +```typescript +import { ArgumentMetadata } from '@nitrostack/core'; + +export interface PipeInterface { + transform(value: T, metadata: ArgumentMetadata): R | Promise; +} +``` + +### Example: +```typescript +import { Pipe, PipeInterface, ArgumentMetadata } from '@nitrostack/core'; + +@Pipe() +export class TrimPipe implements PipeInterface, Record> { + transform(value: Record, metadata: ArgumentMetadata) { + const trimmed: Record = {}; + for (const [key, val] of Object.entries(value)) { + trimmed[key] = typeof val === 'string' ? val.trim() : val; + } + return trimmed; + } +} +``` + +Apply the pipe using `@UsePipes(...)` on a tool method: +```typescript +import { Tool, UsePipes, z } from '@nitrostack/core'; +import { TrimPipe } from './trim.pipe.js'; + +export class MessagingTools { + @Tool({ + name: 'send_message', + description: 'Send a message to other stations.', + inputSchema: z.object({ text: z.string() }), + }) + @UsePipes(TrimPipe) + async sendMessage(input: { text: string }) { + return { sentText: input.text }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/tools-resources-prompts/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/tools-resources-prompts/SKILL.md @@ -0,0 +1,287 @@ +--- +name: nitrostack-tools-resources-prompts +description: Guidelines and patterns for defining Tools, Resources, and Prompts in a NitroStack application with schema validation via Zod, including caching, rate-limiting, and base64 file uploads. +--- + +## When to Use +Use this skill whenever you are defining, editing, or validating tools, resources, or prompts on a NitroStack MCP server. + +## Defining Tools with `@Tool` +An MCP tool exposes a function that an AI client can invoke. Decorate a service or controller method with `@Tool`. + +### Key Tool Options: +* `name`: Kebab-case or snake_case unique identifier. +* `description`: Detailed description explaining when and how the client should use it. +* `inputSchema`: A Zod object schema for strict validation of inputs. +* `outputSchema` (optional): Zod schema validating the output structure. + +```typescript +import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core'; + +@Controller('weather') +export class WeatherService { + @Tool({ + name: 'get_current_weather', + description: 'Get the current weather forecast for a specific city.', + inputSchema: z.object({ + city: z.string().describe('The name of the city, e.g., San Francisco'), + unit: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }) + @InitialTool() // Auto-invoked when the AI client initializes/starts + async getWeather( + input: { city: string; unit: 'celsius' | 'fahrenheit' }, + ctx: ExecutionContext + ) { + ctx.logger.info(`Fetching weather for ${input.city}`); + // implementation + return { + city: input.city, + temp: 22, + condition: 'Sunny', + }; + } +} +``` + +## Defining Resources with `@Resource` +An MCP resource exposes static or dynamic data files/URIs that the AI client can read. + +### Key Resource Options: +* `uri`: URI pattern (e.g., `git://{owner}/{repo}/file` or static `app://config`). +* `name`: Unique name of the resource. +* `description`: Explanation of what data this resource provides. +* `mimeType`: Mime type of the response (e.g., `text/plain`, `application/json`). + +```typescript +import { Resource, ExecutionContext } from '@nitrostack/core'; + +export class ConfigResources { + @Resource({ + uri: 'app://settings', + name: 'Application Settings', + description: 'System-wide configuration settings and parameters.', + mimeType: 'application/json', + }) + async getSettings(ctx: ExecutionContext) { + return { + environment: 'development', + debugMode: true, + }; + } +} +``` + +## Defining Prompts with `@Prompt` +An MCP prompt exposes reusable templates or instruction sets that guide LLMs. + +### Key Prompt Options: +* `name`: Name of the prompt. +* `description`: Describes what task this prompt helps accomplish. +* `arguments`: Declares parameters the client can supply to customize the prompt template. + +```typescript +import { Prompt, ExecutionContext } from '@nitrostack/core'; + +export class PromptTemplates { + @Prompt({ + name: 'code_review', + description: 'Provide an intensive code review for a given code snippet.', + arguments: [ + { name: 'language', description: 'The programming language, e.g., TypeScript', required: true }, + { name: 'code', description: 'The code snippet to review', required: true }, + ], + }) + async getCodeReviewPrompt( + args: { language: string; code: string }, + ctx: ExecutionContext + ) { + return { + messages: [ + { + role: 'user', + content: `You are an expert software engineer. Review this ${args.language} code:\n\n${args.code}`, + }, + ], + }; + } +} +``` + +--- + +## Tool Policies: Caching (`@Cache`) and Rate Limiting (`@RateLimit`) +You can control tool execution behaviors (such as performance optimization and throttling) using method decorators. + +### 1. Caching with `@Cache` +Use `@Cache` to cache tool execution outputs for a specified duration (TTL in seconds). This reduces database or API overhead for frequent identical requests. + +#### Options: +* `ttl`: Cache time-to-live in seconds (required). +* `key` (optional): Custom function `(input: any, context?: any) => string` that returns a unique cache key based on inputs. If not defined, a key is auto-generated from serialized input arguments. + +#### Example: +```typescript +import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core'; + +export class StationTools { + @Tool({ + name: 'get_system_status', + description: 'Fetch real-time station metrics. Response is cached.', + inputSchema: z.object({}), + }) + @Cache({ ttl: 60 }) // Caches status for 60 seconds + async getSystemStatus() { + return { temperature: 21.5, oxygen: 0.98 }; + } + + @Tool({ + name: 'get_crew_status', + description: 'Fetch status of a crew member. Cached by crew ID.', + inputSchema: z.object({ id: z.string() }), + }) + @Cache({ + ttl: 300, + key: (input) => `crew:status:${input.id}` + }) + async getCrewStatus(input: { id: string }) { + // ... + } +} +``` + +### 2. Rate Limiting with `@RateLimit` +Use `@RateLimit` to restrict the number of tool invocations within a specified time window to prevent client abuse. + +#### Options: +* `requests`: Number of allowed requests in the window (required). +* `window`: Throttling duration window (required). Supports formats like `'1s'`, `'1m'`, `'1h'`. +* `key` (optional): Custom function `(context: ExecutionContext) => string` to group rate limits. Useful for rate-limiting per user role or API key. + +#### Example: +```typescript +import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core'; + +export class DiagnosticTools { + @Tool({ + name: 'run_deep_diagnostic', + description: 'Run intensive diagnostics. Rate limited.', + inputSchema: z.object({}), + }) + @RateLimit({ requests: 3, window: '1m' }) // Max 3 requests per minute globally + async runDeepDiagnostic() { + return { diagnosticReport: 'All systems operational.' }; + } + + @Tool({ + name: 'request_supply_drop', + description: 'Request inventory supplies. Rate limited per user.', + inputSchema: z.object({ item: z.string() }), + }) + @RateLimit({ + requests: 5, + window: '1h', + key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous' + }) + async requestSupply(input: { item: string }, ctx: ExecutionContext) { + // ... + } +} +``` + +--- + +## Handling File Uploads in Tools +NitroStack supports file uploads from MCP clients (like NitroStudio) by passing the file as a base64-encoded string inside a tool's input parameters. + +### 1. Declaring Input Schema for File Uploads +To accept an uploaded file, define three Zod fields in your tool's `inputSchema`: +* `file_name`: The name of the file (e.g. `report.csv`). +* `file_type`: The MIME type (e.g. `text/csv`). +* `file_content`: The base64-encoded string containing the file data. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; + +export class FileTools { + @Tool({ + name: 'upload_document', + description: 'Upload a text document or image.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async uploadDocument(input: any, ctx: ExecutionContext) { + // Processing logic + } +} +``` + +### 2. Decoding Base64 Payloads +File uploads can arrive in two formats depending on the client: +1. **Data URL format**: `data:image/png;base64,iVBORw0KGgo...` +2. **Raw Base64 format**: `iVBORw0KGgo...` + +Use the following universal decoder pattern to parse either format into a Node `Buffer`: + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; + +function decodeBase64File(content: string): Buffer { + const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (matches && matches.length === 3) { + // Data URL format - decode matches[2] + return Buffer.from(matches[2], 'base64'); + } else { + // Raw base64 format - decode input directly + return Buffer.from(content, 'base64'); + } +} +``` + +### 3. Secure File Saving Example +Always validate the directory paths to prevent directory traversal attacks when saving files to disk. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); + +export class SecureUploadTools { + @Tool({ + name: 'save_uploaded_file', + description: 'Decodes and saves an uploaded file securely.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async saveFile(input: any, ctx: ExecutionContext) { + // Ensure uploads directory exists + if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + } + + // Secure destination path to prevent path traversal + const safeName = path.basename(input.file_name); + const filePath = path.join(UPLOAD_DIR, safeName); + if (!filePath.startsWith(UPLOAD_DIR)) { + throw new Error('Invalid file path detected (path traversal).'); + } + + // Decode and write to disk + const buffer = decodeBase64File(input.file_content); + fs.writeFileSync(filePath, buffer); + + ctx.logger.info(`Successfully saved file: ${safeName}`); + return { success: true, path: filePath }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/ui-widgets/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.claude/skills/ui-widgets/SKILL.md @@ -0,0 +1,251 @@ +--- +name: nitrostack-ui-widgets +description: Best practices for linking tools to interactive frontend widgets using @Widget and @nitrostack/widgets SDK (including state sync, tool calling, display modes, media queries, and chat actions). +--- + +## When to Use +Use this skill when designing, building, or modifying interactive user interface widgets that display custom React content inside AI clients or NitroStudio. + +--- + +## 1. Backend Definition (`@Widget`) +To display a React-based widget for a tool's output, decorate the tool method with `@Widget`. + +### Options: +* **String Route**: A simple string representing the route identifier in the frontend React app (e.g. `'product-card'`). +* **Object Route**: Object including: + * `route` (required): The route path. + * `domain` (optional): Allowed sandbox domain. + * `csp` (optional): Content Security Policy guidelines. + +### Example: +```typescript +import { Tool, Widget, z } from '@nitrostack/core'; + +export class CatalogTools { + @Tool({ + name: 'fetch_product', + description: 'Get product information by barcode.', + inputSchema: z.object({ barcode: z.string() }), + }) + @Widget('product-details') // Maps to the "product-details" frontend component + async fetchProduct(input: { barcode: string }) { + return { + name: 'Super Nitro Energy Drink', + price: 2.99, + sku: input.barcode, + }; + } +} +``` + +--- + +## 2. Frontend React Widget (`@nitrostack/widgets`) +In your React widget frontend application (typically a Next.js client component), use the `useWidgetSDK` hook to receive input data from the client host. + +### React Component Example: +```tsx +'use client'; + +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +interface ProductData { + name: string; + price: number; + sku: string; +} + +export default function ProductDetailsWidget() { + const { isReady, getToolOutput, theme } = useWidgetSDK(); + const data = getToolOutput(); + + if (!isReady) { + return
Connecting to host...
; + } + + if (!data) { + return
No product data received.
; + } + + return ( +
+

{data.name}

+

${data.price.toFixed(2)}

+ SKU: {data.sku} +
+ ); +} +``` + +--- + +## 3. State Management & Synchronization (`useWidgetState`) +Use `useWidgetState` to manage and persist client-side widget state (e.g. selected tabs, filter values, input states). This state automatically synchronizes with the host application context, persisting it across page re-renders. + +### Example: +```tsx +import React from 'react'; +import { useWidgetState } from '@nitrostack/widgets'; + +export default function StationPanelWidget() { + const [state, setState] = useWidgetState(() => ({ + selectedTab: 'overview', + showExtendedInfo: false, + })); + + return ( +
+ +

Current Tab: {state?.selectedTab}

+
+ ); +} +``` + +--- + +## 4. Calling Core Tools from Widgets (`callTool`) +You can invoke other backend MCP tools directly from the frontend widget using `callTool`. This is useful for tool chaining or triggering detailed audits. + +### Example: +```tsx +import React, { useState } from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function SystemDiagnostics() { + const { callTool, isReady } = useWidgetSDK(); + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState(null); + + const runDiagnostic = async () => { + if (!isReady) return; + setIsRunning(true); + try { + const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' }); + setResult(response.result as string); + } catch (err) { + setResult('Diagnostic execution failed.'); + } finally { + setIsRunning(false); + } + }; + + return ( + + ); +} +``` + +--- + +## 5. Layout & Display Controls +Widgets can dynamically request size mode changes (fullscreen, inline, picture-in-picture) and adapt layouts to safe areas (dynamic islands/notches) or maximum height constraints. + +### Key Methods: +* `requestFullscreen()`: Switch host widget display to fullscreen. +* `requestInline()`: Switch host widget display back to inline. +* `requestPip()`: Float widget in Picture-in-Picture. +* `requestClose()`: Dismiss the widget completely. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function StatusBoard() { + const { + requestFullscreen, + requestInline, + requestClose, + displayMode, // Reactive property ('fullscreen' | 'inline' | 'pip') + maxHeight, // Reactive maxHeight constraint (in pixels) + getSafeArea // Insets data: { top, right, bottom, left } + } = useWidgetSDK(); + + const safeArea = getSafeArea() || { top: 0, bottom: 0 }; + + return ( +
+

Mode: {displayMode}

+ + + +
+ ); +} +``` + +--- + +## 6. Chat Navigation & Actions +Widgets can interact with the host chat pane using external browser links and follow-up prompts. + +### Key Methods: +* `openExternal(url)`: Open the target URL safely in the user's primary external browser. +* `sendFollowUpMessage(prompt)`: Insert a message into the chat flow, automatically submitting it to the LLM agent. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function MissionControl() { + const { openExternal, sendFollowUpMessage } = useWidgetSDK(); + + return ( +
+ {/* Open external documentation */} + + + {/* Ask LLM agent directly from the widget */} + +
+ ); +} +``` + +--- + +## 7. Media & Accessibility Queries +The SDK provides helper utilities to query target client capabilities for styling or accessibility. + +### Key Utilities: +* `prefersReducedMotion()`: Returns `true` if client settings specify reduced motion. Disable animations. +* `isPrimarilyTouchDevice()`: Returns `true` if the device has a coarse pointer (e.g. touch/mobile). Increase button target sizes. +* `isHoverAvailable()`: Returns `true` if pointer supports hover states. +* `prefersDarkColorScheme()`: Returns `true` if the system theme is dark. + +### Example: +```tsx +import React from 'react'; +import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets'; + +export default function AccessiblePanel() { + const isTouch = isPrimarilyTouchDevice(); + const reducedMotion = prefersReducedMotion(); + + return ( +
+ +
+ ); +} +``` + +--- + +## 8. Testing Widgets +* Open your project in **NitroStudio** for visual preview. +* Invoke the tool from the AI chat or testing pane to verify the widget updates instantly with the returned JSON structure. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/auth-security/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/auth-security/SKILL.md @@ -0,0 +1,141 @@ +--- +name: nitrostack-auth-security +description: Best practices for implementing JWT, API Keys, OAuth 2.1, and RBAC in a NitroStack application. +--- + +## When to Use +Use this skill when configuring security modules, implementing user authentication, restricting tool access via guards, or handling sensitive tokens. + +--- + +## 1. JSON Web Tokens (JWT) +To secure tools with JWT authentication: + +### Register `JWTModule`: +```typescript +import { JWTModule, Module, McpApp } from '@nitrostack/core'; + +@McpApp({ + server: { name: 'my-server', version: '1.0.0' } +}) +@Module({ + imports: [ + JWTModule.forRoot({ + secret: process.env.JWT_SECRET!, + expiresIn: '7d', + }), + ] +}) +export class AppModule {} +``` + +### Write a `JWTGuard`: +```typescript +import { Guard, ExecutionContext, Injectable, ConfigService } from '@nitrostack/core'; +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JWTGuard implements Guard { + constructor(private config: ConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const token = this.extractToken(context); + if (!token) return false; + + try { + const secret = this.config.get('JWT_SECRET'); + const payload = jwt.verify(token, secret) as any; + context.auth = { + subject: payload.sub, + role: payload.role, + token, + }; + return true; + } catch { + return false; + } + } + + private extractToken(context: ExecutionContext): string | null { + const auth = context.metadata?.authorization; + if (auth?.startsWith('Bearer ')) { + return auth.substring(7); + } + return null; + } +} +``` + +--- + +## 2. API Key Authentication +Use `ApiKeyModule` for service-to-service validation. + +### Register `ApiKeyModule`: +```typescript +import { ApiKeyModule, Module } from '@nitrostack/core'; + +@Module({ + imports: [ + ApiKeyModule.forRoot({ + keysEnvPrefix: 'API_KEY', // Reads API_KEY_1, API_KEY_2, etc. + headerName: 'x-api-key', + hashed: false, + }), + ] +}) +export class AppModule {} +``` + +### API Key Guard: +```typescript +import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core'; + +export class ApiKeyGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const apiKey = context.metadata?.['x-api-key'] || context.metadata?.apiKey; + if (!apiKey) return false; + + const isValid = await ApiKeyModule.validate(apiKey as string); + if (isValid) { + context.auth = { + subject: `apikey_${(apiKey as string).substring(0, 10)}`, + scopes: ['*'], + }; + return true; + } + return false; + } +} +``` + +--- + +## 3. Role-Based Access Control (RBAC) +Chain guards sequentially to implement user-role authorization. + +```typescript +import { Injectable, Guard, ExecutionContext, UseGuards, Tool, z } from '@nitrostack/core'; +import { JWTGuard } from './jwt.guard.js'; + +@Injectable() +export class AdminGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + // Requires JWTGuard to have populated context.auth first + return context.auth?.role === 'admin'; + } +} + +// Applying chained guards to a tool +export class SystemTools { + @Tool({ + name: 'reset_database', + description: 'Dangerous action: wipes database. Admin only.', + inputSchema: z.object({}), + }) + @UseGuards(JWTGuard, AdminGuard) // Chain auth first, then role check + async resetDatabase() { + return { success: true }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/mcp-app-architecture/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/mcp-app-architecture/SKILL.md @@ -0,0 +1,176 @@ +--- +name: nitrostack-mcp-app-architecture +description: Best practices and guidelines for bootstrapping, defining modules, using dependency injection, managing server lifecycles, and handling events in the NitroStack SDK. +--- + +## When to Use +Use this skill whenever you are bootstrapping a new NitroStack MCP server, creating modules, injecting services, or handling application lifecycle events. + +## Bootstrapping a NitroStack App +A NitroStack application is initialized with the `@McpApp` decorator on a root class, accompanied by a root `@Module`. + +```typescript +import { McpApp, Module } from '@nitrostack/core'; +import { DatabaseModule } from './database/database.module.js'; +import { UsersModule } from './users/users.module.js'; + +@McpApp({ + module: AppModule, + server: { + name: 'user-management-server', + version: '1.0.0', + }, +}) +@Module({ + imports: [DatabaseModule, UsersModule], +}) +export class AppModule {} +``` + +## Modules +Modules organize your application structure. Use the `@Module` decorator to define imports, exports, and providers. + +* **`imports`**: Other modules whose exported providers should be available in this module. +* **`providers`**: Services, tools, resources, or prompts that should be instantiated and managed by the DI container within this module. +* **`exports`**: Providers defined in this module that should be visible to other modules importing this one. + +```typescript +import { Module } from '@nitrostack/core'; +import { UsersService } from './users.service.js'; +import { UsersTools } from './users.tools.js'; + +@Module({ + providers: [UsersService, UsersTools], + exports: [UsersService], +}) + +## Controllers +Use the `@ControllerDecorator` (or alias it as `@Controller`) to group tools, resources, and prompts together. Controllers are automatically registered as singletons in the DI container. + +### Key Controller Options: +* **`prefix`**: A string prefix applied to every `@Tool` defined in this controller. For example, `@ControllerDecorator('github')` prefixing a tool named `create_issue` exposes it to MCP clients as `github_create_issue`. + +```typescript +import { ControllerDecorator as Controller, Tool, ExecutionContext } from '@nitrostack/core'; + +@Controller('github') +export class GitHubController { + @Tool({ + name: 'create_issue', + description: 'Create an issue in a repository', + inputSchema: z.object({ /* ... */ }) + }) + async createIssue(input: any, ctx: ExecutionContext) { + // Exposed to clients as "github_create_issue" + } +} +``` + +## Dependency Injection (DI) +NitroStack uses a robust dependency injection container to manage class instances and lifecycles. + +### Injection Lifecycles +1. **Singleton (Default)**: A single instance is shared across the entire application. +2. **Transient**: A new instance is created every time it is resolved/injected. +3. **Scoped**: A new instance is created per incoming request or context. + +```typescript +import { Injectable, Scope } from '@nitrostack/core'; + +@Injectable({ scope: Scope.SINGLETON }) +export class UsersService { + constructor(private readonly db: DatabaseService) {} + + async getUser(id: string) { + return this.db.query('SELECT * FROM users WHERE id = $1', [id]); + } +} +``` + +## Lifecycles and Hooks +Implement NestJS-style lifecycle interfaces on modules, controllers, or providers to hook into application state changes: + +* **`OnModuleInit`** (`onModuleInit`): Called after modules have initialized but before the server starts listening. +* **`OnApplicationBootstrap`** (`onApplicationBootstrap`): Called once the server is fully started and listening. +* **`OnModuleDestroy`** (`onModuleDestroy`): Called when the module or application is shutting down. +* **`BeforeApplicationShutdown`** (`beforeApplicationShutdown(signal?: string)`): Called before the application starts shutting down. Receives the OS signal (e.g. `SIGINT`). +* **`OnApplicationShutdown`** (`onApplicationShutdown(signal?: string)`): Called during shutdown. Receives the OS signal. + +```typescript +import { + Injectable, + OnModuleInit, + OnApplicationBootstrap, + OnModuleDestroy, + BeforeApplicationShutdown, + OnApplicationShutdown +} from '@nitrostack/core'; + +@Injectable() +export class DatabaseService + implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown +{ + async onModuleInit() { + await this.connect(); + } + + async onApplicationBootstrap() { + console.log('App ready to handle connections.'); + } + + async onModuleDestroy() { + await this.cleanupPendingQueries(); + } + + async beforeApplicationShutdown(signal?: string) { + console.log(`Shutting down soon (signal: ${signal}).`); + } + + async onApplicationShutdown(signal?: string) { + await this.disconnect(); + } +} +``` + +--- + +## Eventing System (`emitEvent` and `@OnEvent`) +NitroStack includes an internal eventing system to decouple components. A service or tool can emit an event using `emitEvent`, and any injectable class (like a handler service or controller) can subscribe using the `@OnEvent` decorator. + +### 1. Emitting Events +Call `emitEvent` to dispatch an event payload asynchronously. + +```typescript +import { Injectable, emitEvent } from '@nitrostack/core'; + +@Injectable() +export class SpaceShipService { + async launchShip(shipId: string) { + // Process launch... + + // Dispatch event + emitEvent('ship.launched', { + shipId, + timestamp: new Date().toISOString(), + }); + } +} +``` + +### 2. Listening to Events +Decorate a method inside any `@Injectable()` class with `@OnEvent('event_pattern')` to register it as an event handler. + +```typescript +import { Injectable, OnEvent } from '@nitrostack/core'; + +@Injectable({ deps: [] }) +export class FlightLogHandler { + @OnEvent('ship.launched') + async logLaunch(data: { shipId: string; timestamp: string }) { + console.error(`🚀 [EVENT] Ship ${data.shipId} was successfully launched at ${data.timestamp}`); + } +} +``` + +> [!NOTE] +> For the `@OnEvent` decorator to register properly, the containing class must be declared as a provider inside an active module. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/middleware-pipeline/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/middleware-pipeline/SKILL.md @@ -0,0 +1,235 @@ +--- +name: nitrostack-middleware-pipeline +description: Best practices for implementing and applying Guards, Interceptors, Middleware, Pipes, and Exception Filters in the NitroStack SDK. +--- + +## When to Use +Use this skill when implementing request validation, authorization checks, response mapping, logging, error handling, or performance tracking on NitroStack tool methods. + +--- + +## 1. Guards (`Guard` and `@UseGuards`) +Guards determine if a request should be processed by a tool handler based on authentication, authorization, or other conditions. + +### Interface: +```typescript +import { Guard, ExecutionContext } from '@nitrostack/core'; + +export interface Guard { + canActivate(context: ExecutionContext): boolean | Promise; +} +``` + +### Example: +```typescript +import { Guard, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class RolesGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const userRoles = context.clientMetadata?.roles || []; + return userRoles.includes('admin'); + } +} +``` + +Apply the guard using `@UseGuards(...)`: +```typescript +import { Tool, UseGuards, z } from '@nitrostack/core'; +import { RolesGuard } from './roles.guard.js'; + +export class AdminTools { + @Tool({ + name: 'delete_system_logs', + description: 'Delete all system logs from the server.', + inputSchema: z.object({}), + }) + @UseGuards(RolesGuard) + async deleteLogs() { + return { success: true }; + } +} +``` + +--- + +## 2. Interceptors (`InterceptorInterface` and `@UseInterceptors`) +Interceptors can transform/intercept input arguments or mapped output from a tool method execution. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface InterceptorInterface { + intercept(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { InterceptorInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class TimingInterceptor implements InterceptorInterface { + async intercept(context: ExecutionContext, next: () => Promise): Promise { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + context.logger.info(`Execution took ${duration}ms`); + return { + ...result, + _meta: { durationMs: duration } + }; + } +} +``` + +--- + +## 3. Exception Filters (`ExceptionFilterInterface` and `@UseFilters`) +Exception filters catch any errors thrown within guards, interceptors, or the tool handlers themselves, mapping them into user-friendly JSON payloads. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext): unknown | Promise; +} +``` + +### Example: +```typescript +import { ExceptionFilterInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class CustomExceptionFilter implements ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext) { + const message = exception instanceof Error ? exception.message : 'Unknown error'; + return { + error: true, + message, + timestamp: new Date().toISOString() + }; + } +} +``` + +Apply the filter using `@UseFilters(...)` on a tool method: + +```typescript +import { Tool, UseFilters, z } from '@nitrostack/core'; +import { CustomExceptionFilter } from './custom-exception.filter.js'; + +export class LoggingTools { + @Tool({ + name: 'generate_report', + description: 'Generates system usage reports.', + inputSchema: z.object({}), + }) + @UseFilters(CustomExceptionFilter) + async generateReport() { + throw new Error('Report generation is not implemented yet.'); + } +} +``` + +--- + +## 4. Middleware (`MiddlewareInterface`, `@Middleware` and `@UseMiddleware`) +Middleware executes before the request reaches the tool handler, and can wrap the handler execution by invoking `next()`. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface MiddlewareInterface { + use(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { Middleware, MiddlewareInterface, ExecutionContext } from '@nitrostack/core'; + +@Middleware() +export class LoggingMiddleware implements MiddlewareInterface { + async use(context: ExecutionContext, next: () => Promise): Promise { + context.logger.info(`Entering tool: ${context.toolName}`); + try { + const result = await next(); + context.logger.info(`Exiting tool: ${context.toolName}`); + return result; + } catch (error) { + context.logger.error(`Error in tool: ${error}`); + throw error; + } + } +} +``` + +Apply the middleware using `@UseMiddleware(...)` on a tool method: +```typescript +import { Tool, UseMiddleware, z } from '@nitrostack/core'; +import { LoggingMiddleware } from './logging.middleware.js'; + +export class StationTools { + @Tool({ + name: 'fetch_logs', + description: 'Fetch station operations logs.', + inputSchema: z.object({}), + }) + @UseMiddleware(LoggingMiddleware) + async fetchLogs() { + return { status: 'operational' }; + } +} +``` + +--- + +## 5. Pipes (`PipeInterface`, `@Pipe` and `@UsePipes`) +Pipes are used to transform or validate input arguments before they reach the tool handler method. + +### Interface: +```typescript +import { ArgumentMetadata } from '@nitrostack/core'; + +export interface PipeInterface { + transform(value: T, metadata: ArgumentMetadata): R | Promise; +} +``` + +### Example: +```typescript +import { Pipe, PipeInterface, ArgumentMetadata } from '@nitrostack/core'; + +@Pipe() +export class TrimPipe implements PipeInterface, Record> { + transform(value: Record, metadata: ArgumentMetadata) { + const trimmed: Record = {}; + for (const [key, val] of Object.entries(value)) { + trimmed[key] = typeof val === 'string' ? val.trim() : val; + } + return trimmed; + } +} +``` + +Apply the pipe using `@UsePipes(...)` on a tool method: +```typescript +import { Tool, UsePipes, z } from '@nitrostack/core'; +import { TrimPipe } from './trim.pipe.js'; + +export class MessagingTools { + @Tool({ + name: 'send_message', + description: 'Send a message to other stations.', + inputSchema: z.object({ text: z.string() }), + }) + @UsePipes(TrimPipe) + async sendMessage(input: { text: string }) { + return { sentText: input.text }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/tools-resources-prompts/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/tools-resources-prompts/SKILL.md @@ -0,0 +1,287 @@ +--- +name: nitrostack-tools-resources-prompts +description: Guidelines and patterns for defining Tools, Resources, and Prompts in a NitroStack application with schema validation via Zod, including caching, rate-limiting, and base64 file uploads. +--- + +## When to Use +Use this skill whenever you are defining, editing, or validating tools, resources, or prompts on a NitroStack MCP server. + +## Defining Tools with `@Tool` +An MCP tool exposes a function that an AI client can invoke. Decorate a service or controller method with `@Tool`. + +### Key Tool Options: +* `name`: Kebab-case or snake_case unique identifier. +* `description`: Detailed description explaining when and how the client should use it. +* `inputSchema`: A Zod object schema for strict validation of inputs. +* `outputSchema` (optional): Zod schema validating the output structure. + +```typescript +import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core'; + +@Controller('weather') +export class WeatherService { + @Tool({ + name: 'get_current_weather', + description: 'Get the current weather forecast for a specific city.', + inputSchema: z.object({ + city: z.string().describe('The name of the city, e.g., San Francisco'), + unit: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }) + @InitialTool() // Auto-invoked when the AI client initializes/starts + async getWeather( + input: { city: string; unit: 'celsius' | 'fahrenheit' }, + ctx: ExecutionContext + ) { + ctx.logger.info(`Fetching weather for ${input.city}`); + // implementation + return { + city: input.city, + temp: 22, + condition: 'Sunny', + }; + } +} +``` + +## Defining Resources with `@Resource` +An MCP resource exposes static or dynamic data files/URIs that the AI client can read. + +### Key Resource Options: +* `uri`: URI pattern (e.g., `git://{owner}/{repo}/file` or static `app://config`). +* `name`: Unique name of the resource. +* `description`: Explanation of what data this resource provides. +* `mimeType`: Mime type of the response (e.g., `text/plain`, `application/json`). + +```typescript +import { Resource, ExecutionContext } from '@nitrostack/core'; + +export class ConfigResources { + @Resource({ + uri: 'app://settings', + name: 'Application Settings', + description: 'System-wide configuration settings and parameters.', + mimeType: 'application/json', + }) + async getSettings(ctx: ExecutionContext) { + return { + environment: 'development', + debugMode: true, + }; + } +} +``` + +## Defining Prompts with `@Prompt` +An MCP prompt exposes reusable templates or instruction sets that guide LLMs. + +### Key Prompt Options: +* `name`: Name of the prompt. +* `description`: Describes what task this prompt helps accomplish. +* `arguments`: Declares parameters the client can supply to customize the prompt template. + +```typescript +import { Prompt, ExecutionContext } from '@nitrostack/core'; + +export class PromptTemplates { + @Prompt({ + name: 'code_review', + description: 'Provide an intensive code review for a given code snippet.', + arguments: [ + { name: 'language', description: 'The programming language, e.g., TypeScript', required: true }, + { name: 'code', description: 'The code snippet to review', required: true }, + ], + }) + async getCodeReviewPrompt( + args: { language: string; code: string }, + ctx: ExecutionContext + ) { + return { + messages: [ + { + role: 'user', + content: `You are an expert software engineer. Review this ${args.language} code:\n\n${args.code}`, + }, + ], + }; + } +} +``` + +--- + +## Tool Policies: Caching (`@Cache`) and Rate Limiting (`@RateLimit`) +You can control tool execution behaviors (such as performance optimization and throttling) using method decorators. + +### 1. Caching with `@Cache` +Use `@Cache` to cache tool execution outputs for a specified duration (TTL in seconds). This reduces database or API overhead for frequent identical requests. + +#### Options: +* `ttl`: Cache time-to-live in seconds (required). +* `key` (optional): Custom function `(input: any, context?: any) => string` that returns a unique cache key based on inputs. If not defined, a key is auto-generated from serialized input arguments. + +#### Example: +```typescript +import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core'; + +export class StationTools { + @Tool({ + name: 'get_system_status', + description: 'Fetch real-time station metrics. Response is cached.', + inputSchema: z.object({}), + }) + @Cache({ ttl: 60 }) // Caches status for 60 seconds + async getSystemStatus() { + return { temperature: 21.5, oxygen: 0.98 }; + } + + @Tool({ + name: 'get_crew_status', + description: 'Fetch status of a crew member. Cached by crew ID.', + inputSchema: z.object({ id: z.string() }), + }) + @Cache({ + ttl: 300, + key: (input) => `crew:status:${input.id}` + }) + async getCrewStatus(input: { id: string }) { + // ... + } +} +``` + +### 2. Rate Limiting with `@RateLimit` +Use `@RateLimit` to restrict the number of tool invocations within a specified time window to prevent client abuse. + +#### Options: +* `requests`: Number of allowed requests in the window (required). +* `window`: Throttling duration window (required). Supports formats like `'1s'`, `'1m'`, `'1h'`. +* `key` (optional): Custom function `(context: ExecutionContext) => string` to group rate limits. Useful for rate-limiting per user role or API key. + +#### Example: +```typescript +import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core'; + +export class DiagnosticTools { + @Tool({ + name: 'run_deep_diagnostic', + description: 'Run intensive diagnostics. Rate limited.', + inputSchema: z.object({}), + }) + @RateLimit({ requests: 3, window: '1m' }) // Max 3 requests per minute globally + async runDeepDiagnostic() { + return { diagnosticReport: 'All systems operational.' }; + } + + @Tool({ + name: 'request_supply_drop', + description: 'Request inventory supplies. Rate limited per user.', + inputSchema: z.object({ item: z.string() }), + }) + @RateLimit({ + requests: 5, + window: '1h', + key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous' + }) + async requestSupply(input: { item: string }, ctx: ExecutionContext) { + // ... + } +} +``` + +--- + +## Handling File Uploads in Tools +NitroStack supports file uploads from MCP clients (like NitroStudio) by passing the file as a base64-encoded string inside a tool's input parameters. + +### 1. Declaring Input Schema for File Uploads +To accept an uploaded file, define three Zod fields in your tool's `inputSchema`: +* `file_name`: The name of the file (e.g. `report.csv`). +* `file_type`: The MIME type (e.g. `text/csv`). +* `file_content`: The base64-encoded string containing the file data. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; + +export class FileTools { + @Tool({ + name: 'upload_document', + description: 'Upload a text document or image.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async uploadDocument(input: any, ctx: ExecutionContext) { + // Processing logic + } +} +``` + +### 2. Decoding Base64 Payloads +File uploads can arrive in two formats depending on the client: +1. **Data URL format**: `data:image/png;base64,iVBORw0KGgo...` +2. **Raw Base64 format**: `iVBORw0KGgo...` + +Use the following universal decoder pattern to parse either format into a Node `Buffer`: + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; + +function decodeBase64File(content: string): Buffer { + const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (matches && matches.length === 3) { + // Data URL format - decode matches[2] + return Buffer.from(matches[2], 'base64'); + } else { + // Raw base64 format - decode input directly + return Buffer.from(content, 'base64'); + } +} +``` + +### 3. Secure File Saving Example +Always validate the directory paths to prevent directory traversal attacks when saving files to disk. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); + +export class SecureUploadTools { + @Tool({ + name: 'save_uploaded_file', + description: 'Decodes and saves an uploaded file securely.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async saveFile(input: any, ctx: ExecutionContext) { + // Ensure uploads directory exists + if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + } + + // Secure destination path to prevent path traversal + const safeName = path.basename(input.file_name); + const filePath = path.join(UPLOAD_DIR, safeName); + if (!filePath.startsWith(UPLOAD_DIR)) { + throw new Error('Invalid file path detected (path traversal).'); + } + + // Decode and write to disk + const buffer = decodeBase64File(input.file_content); + fs.writeFileSync(filePath, buffer); + + ctx.logger.info(`Successfully saved file: ${safeName}`); + return { success: true, path: filePath }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/ui-widgets/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.codex/skills/ui-widgets/SKILL.md @@ -0,0 +1,251 @@ +--- +name: nitrostack-ui-widgets +description: Best practices for linking tools to interactive frontend widgets using @Widget and @nitrostack/widgets SDK (including state sync, tool calling, display modes, media queries, and chat actions). +--- + +## When to Use +Use this skill when designing, building, or modifying interactive user interface widgets that display custom React content inside AI clients or NitroStudio. + +--- + +## 1. Backend Definition (`@Widget`) +To display a React-based widget for a tool's output, decorate the tool method with `@Widget`. + +### Options: +* **String Route**: A simple string representing the route identifier in the frontend React app (e.g. `'product-card'`). +* **Object Route**: Object including: + * `route` (required): The route path. + * `domain` (optional): Allowed sandbox domain. + * `csp` (optional): Content Security Policy guidelines. + +### Example: +```typescript +import { Tool, Widget, z } from '@nitrostack/core'; + +export class CatalogTools { + @Tool({ + name: 'fetch_product', + description: 'Get product information by barcode.', + inputSchema: z.object({ barcode: z.string() }), + }) + @Widget('product-details') // Maps to the "product-details" frontend component + async fetchProduct(input: { barcode: string }) { + return { + name: 'Super Nitro Energy Drink', + price: 2.99, + sku: input.barcode, + }; + } +} +``` + +--- + +## 2. Frontend React Widget (`@nitrostack/widgets`) +In your React widget frontend application (typically a Next.js client component), use the `useWidgetSDK` hook to receive input data from the client host. + +### React Component Example: +```tsx +'use client'; + +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +interface ProductData { + name: string; + price: number; + sku: string; +} + +export default function ProductDetailsWidget() { + const { isReady, getToolOutput, theme } = useWidgetSDK(); + const data = getToolOutput(); + + if (!isReady) { + return
Connecting to host...
; + } + + if (!data) { + return
No product data received.
; + } + + return ( +
+

{data.name}

+

${data.price.toFixed(2)}

+ SKU: {data.sku} +
+ ); +} +``` + +--- + +## 3. State Management & Synchronization (`useWidgetState`) +Use `useWidgetState` to manage and persist client-side widget state (e.g. selected tabs, filter values, input states). This state automatically synchronizes with the host application context, persisting it across page re-renders. + +### Example: +```tsx +import React from 'react'; +import { useWidgetState } from '@nitrostack/widgets'; + +export default function StationPanelWidget() { + const [state, setState] = useWidgetState(() => ({ + selectedTab: 'overview', + showExtendedInfo: false, + })); + + return ( +
+ +

Current Tab: {state?.selectedTab}

+
+ ); +} +``` + +--- + +## 4. Calling Core Tools from Widgets (`callTool`) +You can invoke other backend MCP tools directly from the frontend widget using `callTool`. This is useful for tool chaining or triggering detailed audits. + +### Example: +```tsx +import React, { useState } from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function SystemDiagnostics() { + const { callTool, isReady } = useWidgetSDK(); + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState(null); + + const runDiagnostic = async () => { + if (!isReady) return; + setIsRunning(true); + try { + const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' }); + setResult(response.result as string); + } catch (err) { + setResult('Diagnostic execution failed.'); + } finally { + setIsRunning(false); + } + }; + + return ( + + ); +} +``` + +--- + +## 5. Layout & Display Controls +Widgets can dynamically request size mode changes (fullscreen, inline, picture-in-picture) and adapt layouts to safe areas (dynamic islands/notches) or maximum height constraints. + +### Key Methods: +* `requestFullscreen()`: Switch host widget display to fullscreen. +* `requestInline()`: Switch host widget display back to inline. +* `requestPip()`: Float widget in Picture-in-Picture. +* `requestClose()`: Dismiss the widget completely. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function StatusBoard() { + const { + requestFullscreen, + requestInline, + requestClose, + displayMode, // Reactive property ('fullscreen' | 'inline' | 'pip') + maxHeight, // Reactive maxHeight constraint (in pixels) + getSafeArea // Insets data: { top, right, bottom, left } + } = useWidgetSDK(); + + const safeArea = getSafeArea() || { top: 0, bottom: 0 }; + + return ( +
+

Mode: {displayMode}

+ + + +
+ ); +} +``` + +--- + +## 6. Chat Navigation & Actions +Widgets can interact with the host chat pane using external browser links and follow-up prompts. + +### Key Methods: +* `openExternal(url)`: Open the target URL safely in the user's primary external browser. +* `sendFollowUpMessage(prompt)`: Insert a message into the chat flow, automatically submitting it to the LLM agent. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function MissionControl() { + const { openExternal, sendFollowUpMessage } = useWidgetSDK(); + + return ( +
+ {/* Open external documentation */} + + + {/* Ask LLM agent directly from the widget */} + +
+ ); +} +``` + +--- + +## 7. Media & Accessibility Queries +The SDK provides helper utilities to query target client capabilities for styling or accessibility. + +### Key Utilities: +* `prefersReducedMotion()`: Returns `true` if client settings specify reduced motion. Disable animations. +* `isPrimarilyTouchDevice()`: Returns `true` if the device has a coarse pointer (e.g. touch/mobile). Increase button target sizes. +* `isHoverAvailable()`: Returns `true` if pointer supports hover states. +* `prefersDarkColorScheme()`: Returns `true` if the system theme is dark. + +### Example: +```tsx +import React from 'react'; +import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets'; + +export default function AccessiblePanel() { + const isTouch = isPrimarilyTouchDevice(); + const reducedMotion = prefersReducedMotion(); + + return ( +
+ +
+ ); +} +``` + +--- + +## 8. Testing Widgets +* Open your project in **NitroStudio** for visual preview. +* Invoke the tool from the AI chat or testing pane to verify the widget updates instantly with the returned JSON structure. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/auth-security/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/auth-security/SKILL.md @@ -0,0 +1,141 @@ +--- +name: nitrostack-auth-security +description: Best practices for implementing JWT, API Keys, OAuth 2.1, and RBAC in a NitroStack application. +--- + +## When to Use +Use this skill when configuring security modules, implementing user authentication, restricting tool access via guards, or handling sensitive tokens. + +--- + +## 1. JSON Web Tokens (JWT) +To secure tools with JWT authentication: + +### Register `JWTModule`: +```typescript +import { JWTModule, Module, McpApp } from '@nitrostack/core'; + +@McpApp({ + server: { name: 'my-server', version: '1.0.0' } +}) +@Module({ + imports: [ + JWTModule.forRoot({ + secret: process.env.JWT_SECRET!, + expiresIn: '7d', + }), + ] +}) +export class AppModule {} +``` + +### Write a `JWTGuard`: +```typescript +import { Guard, ExecutionContext, Injectable, ConfigService } from '@nitrostack/core'; +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JWTGuard implements Guard { + constructor(private config: ConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const token = this.extractToken(context); + if (!token) return false; + + try { + const secret = this.config.get('JWT_SECRET'); + const payload = jwt.verify(token, secret) as any; + context.auth = { + subject: payload.sub, + role: payload.role, + token, + }; + return true; + } catch { + return false; + } + } + + private extractToken(context: ExecutionContext): string | null { + const auth = context.metadata?.authorization; + if (auth?.startsWith('Bearer ')) { + return auth.substring(7); + } + return null; + } +} +``` + +--- + +## 2. API Key Authentication +Use `ApiKeyModule` for service-to-service validation. + +### Register `ApiKeyModule`: +```typescript +import { ApiKeyModule, Module } from '@nitrostack/core'; + +@Module({ + imports: [ + ApiKeyModule.forRoot({ + keysEnvPrefix: 'API_KEY', // Reads API_KEY_1, API_KEY_2, etc. + headerName: 'x-api-key', + hashed: false, + }), + ] +}) +export class AppModule {} +``` + +### API Key Guard: +```typescript +import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core'; + +export class ApiKeyGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const apiKey = context.metadata?.['x-api-key'] || context.metadata?.apiKey; + if (!apiKey) return false; + + const isValid = await ApiKeyModule.validate(apiKey as string); + if (isValid) { + context.auth = { + subject: `apikey_${(apiKey as string).substring(0, 10)}`, + scopes: ['*'], + }; + return true; + } + return false; + } +} +``` + +--- + +## 3. Role-Based Access Control (RBAC) +Chain guards sequentially to implement user-role authorization. + +```typescript +import { Injectable, Guard, ExecutionContext, UseGuards, Tool, z } from '@nitrostack/core'; +import { JWTGuard } from './jwt.guard.js'; + +@Injectable() +export class AdminGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + // Requires JWTGuard to have populated context.auth first + return context.auth?.role === 'admin'; + } +} + +// Applying chained guards to a tool +export class SystemTools { + @Tool({ + name: 'reset_database', + description: 'Dangerous action: wipes database. Admin only.', + inputSchema: z.object({}), + }) + @UseGuards(JWTGuard, AdminGuard) // Chain auth first, then role check + async resetDatabase() { + return { success: true }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/mcp-app-architecture/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/mcp-app-architecture/SKILL.md @@ -0,0 +1,176 @@ +--- +name: nitrostack-mcp-app-architecture +description: Best practices and guidelines for bootstrapping, defining modules, using dependency injection, managing server lifecycles, and handling events in the NitroStack SDK. +--- + +## When to Use +Use this skill whenever you are bootstrapping a new NitroStack MCP server, creating modules, injecting services, or handling application lifecycle events. + +## Bootstrapping a NitroStack App +A NitroStack application is initialized with the `@McpApp` decorator on a root class, accompanied by a root `@Module`. + +```typescript +import { McpApp, Module } from '@nitrostack/core'; +import { DatabaseModule } from './database/database.module.js'; +import { UsersModule } from './users/users.module.js'; + +@McpApp({ + module: AppModule, + server: { + name: 'user-management-server', + version: '1.0.0', + }, +}) +@Module({ + imports: [DatabaseModule, UsersModule], +}) +export class AppModule {} +``` + +## Modules +Modules organize your application structure. Use the `@Module` decorator to define imports, exports, and providers. + +* **`imports`**: Other modules whose exported providers should be available in this module. +* **`providers`**: Services, tools, resources, or prompts that should be instantiated and managed by the DI container within this module. +* **`exports`**: Providers defined in this module that should be visible to other modules importing this one. + +```typescript +import { Module } from '@nitrostack/core'; +import { UsersService } from './users.service.js'; +import { UsersTools } from './users.tools.js'; + +@Module({ + providers: [UsersService, UsersTools], + exports: [UsersService], +}) + +## Controllers +Use the `@ControllerDecorator` (or alias it as `@Controller`) to group tools, resources, and prompts together. Controllers are automatically registered as singletons in the DI container. + +### Key Controller Options: +* **`prefix`**: A string prefix applied to every `@Tool` defined in this controller. For example, `@ControllerDecorator('github')` prefixing a tool named `create_issue` exposes it to MCP clients as `github_create_issue`. + +```typescript +import { ControllerDecorator as Controller, Tool, ExecutionContext } from '@nitrostack/core'; + +@Controller('github') +export class GitHubController { + @Tool({ + name: 'create_issue', + description: 'Create an issue in a repository', + inputSchema: z.object({ /* ... */ }) + }) + async createIssue(input: any, ctx: ExecutionContext) { + // Exposed to clients as "github_create_issue" + } +} +``` + +## Dependency Injection (DI) +NitroStack uses a robust dependency injection container to manage class instances and lifecycles. + +### Injection Lifecycles +1. **Singleton (Default)**: A single instance is shared across the entire application. +2. **Transient**: A new instance is created every time it is resolved/injected. +3. **Scoped**: A new instance is created per incoming request or context. + +```typescript +import { Injectable, Scope } from '@nitrostack/core'; + +@Injectable({ scope: Scope.SINGLETON }) +export class UsersService { + constructor(private readonly db: DatabaseService) {} + + async getUser(id: string) { + return this.db.query('SELECT * FROM users WHERE id = $1', [id]); + } +} +``` + +## Lifecycles and Hooks +Implement NestJS-style lifecycle interfaces on modules, controllers, or providers to hook into application state changes: + +* **`OnModuleInit`** (`onModuleInit`): Called after modules have initialized but before the server starts listening. +* **`OnApplicationBootstrap`** (`onApplicationBootstrap`): Called once the server is fully started and listening. +* **`OnModuleDestroy`** (`onModuleDestroy`): Called when the module or application is shutting down. +* **`BeforeApplicationShutdown`** (`beforeApplicationShutdown(signal?: string)`): Called before the application starts shutting down. Receives the OS signal (e.g. `SIGINT`). +* **`OnApplicationShutdown`** (`onApplicationShutdown(signal?: string)`): Called during shutdown. Receives the OS signal. + +```typescript +import { + Injectable, + OnModuleInit, + OnApplicationBootstrap, + OnModuleDestroy, + BeforeApplicationShutdown, + OnApplicationShutdown +} from '@nitrostack/core'; + +@Injectable() +export class DatabaseService + implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown +{ + async onModuleInit() { + await this.connect(); + } + + async onApplicationBootstrap() { + console.log('App ready to handle connections.'); + } + + async onModuleDestroy() { + await this.cleanupPendingQueries(); + } + + async beforeApplicationShutdown(signal?: string) { + console.log(`Shutting down soon (signal: ${signal}).`); + } + + async onApplicationShutdown(signal?: string) { + await this.disconnect(); + } +} +``` + +--- + +## Eventing System (`emitEvent` and `@OnEvent`) +NitroStack includes an internal eventing system to decouple components. A service or tool can emit an event using `emitEvent`, and any injectable class (like a handler service or controller) can subscribe using the `@OnEvent` decorator. + +### 1. Emitting Events +Call `emitEvent` to dispatch an event payload asynchronously. + +```typescript +import { Injectable, emitEvent } from '@nitrostack/core'; + +@Injectable() +export class SpaceShipService { + async launchShip(shipId: string) { + // Process launch... + + // Dispatch event + emitEvent('ship.launched', { + shipId, + timestamp: new Date().toISOString(), + }); + } +} +``` + +### 2. Listening to Events +Decorate a method inside any `@Injectable()` class with `@OnEvent('event_pattern')` to register it as an event handler. + +```typescript +import { Injectable, OnEvent } from '@nitrostack/core'; + +@Injectable({ deps: [] }) +export class FlightLogHandler { + @OnEvent('ship.launched') + async logLaunch(data: { shipId: string; timestamp: string }) { + console.error(`🚀 [EVENT] Ship ${data.shipId} was successfully launched at ${data.timestamp}`); + } +} +``` + +> [!NOTE] +> For the `@OnEvent` decorator to register properly, the containing class must be declared as a provider inside an active module. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/middleware-pipeline/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/middleware-pipeline/SKILL.md @@ -0,0 +1,235 @@ +--- +name: nitrostack-middleware-pipeline +description: Best practices for implementing and applying Guards, Interceptors, Middleware, Pipes, and Exception Filters in the NitroStack SDK. +--- + +## When to Use +Use this skill when implementing request validation, authorization checks, response mapping, logging, error handling, or performance tracking on NitroStack tool methods. + +--- + +## 1. Guards (`Guard` and `@UseGuards`) +Guards determine if a request should be processed by a tool handler based on authentication, authorization, or other conditions. + +### Interface: +```typescript +import { Guard, ExecutionContext } from '@nitrostack/core'; + +export interface Guard { + canActivate(context: ExecutionContext): boolean | Promise; +} +``` + +### Example: +```typescript +import { Guard, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class RolesGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const userRoles = context.clientMetadata?.roles || []; + return userRoles.includes('admin'); + } +} +``` + +Apply the guard using `@UseGuards(...)`: +```typescript +import { Tool, UseGuards, z } from '@nitrostack/core'; +import { RolesGuard } from './roles.guard.js'; + +export class AdminTools { + @Tool({ + name: 'delete_system_logs', + description: 'Delete all system logs from the server.', + inputSchema: z.object({}), + }) + @UseGuards(RolesGuard) + async deleteLogs() { + return { success: true }; + } +} +``` + +--- + +## 2. Interceptors (`InterceptorInterface` and `@UseInterceptors`) +Interceptors can transform/intercept input arguments or mapped output from a tool method execution. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface InterceptorInterface { + intercept(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { InterceptorInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class TimingInterceptor implements InterceptorInterface { + async intercept(context: ExecutionContext, next: () => Promise): Promise { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + context.logger.info(`Execution took ${duration}ms`); + return { + ...result, + _meta: { durationMs: duration } + }; + } +} +``` + +--- + +## 3. Exception Filters (`ExceptionFilterInterface` and `@UseFilters`) +Exception filters catch any errors thrown within guards, interceptors, or the tool handlers themselves, mapping them into user-friendly JSON payloads. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext): unknown | Promise; +} +``` + +### Example: +```typescript +import { ExceptionFilterInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class CustomExceptionFilter implements ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext) { + const message = exception instanceof Error ? exception.message : 'Unknown error'; + return { + error: true, + message, + timestamp: new Date().toISOString() + }; + } +} +``` + +Apply the filter using `@UseFilters(...)` on a tool method: + +```typescript +import { Tool, UseFilters, z } from '@nitrostack/core'; +import { CustomExceptionFilter } from './custom-exception.filter.js'; + +export class LoggingTools { + @Tool({ + name: 'generate_report', + description: 'Generates system usage reports.', + inputSchema: z.object({}), + }) + @UseFilters(CustomExceptionFilter) + async generateReport() { + throw new Error('Report generation is not implemented yet.'); + } +} +``` + +--- + +## 4. Middleware (`MiddlewareInterface`, `@Middleware` and `@UseMiddleware`) +Middleware executes before the request reaches the tool handler, and can wrap the handler execution by invoking `next()`. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface MiddlewareInterface { + use(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { Middleware, MiddlewareInterface, ExecutionContext } from '@nitrostack/core'; + +@Middleware() +export class LoggingMiddleware implements MiddlewareInterface { + async use(context: ExecutionContext, next: () => Promise): Promise { + context.logger.info(`Entering tool: ${context.toolName}`); + try { + const result = await next(); + context.logger.info(`Exiting tool: ${context.toolName}`); + return result; + } catch (error) { + context.logger.error(`Error in tool: ${error}`); + throw error; + } + } +} +``` + +Apply the middleware using `@UseMiddleware(...)` on a tool method: +```typescript +import { Tool, UseMiddleware, z } from '@nitrostack/core'; +import { LoggingMiddleware } from './logging.middleware.js'; + +export class StationTools { + @Tool({ + name: 'fetch_logs', + description: 'Fetch station operations logs.', + inputSchema: z.object({}), + }) + @UseMiddleware(LoggingMiddleware) + async fetchLogs() { + return { status: 'operational' }; + } +} +``` + +--- + +## 5. Pipes (`PipeInterface`, `@Pipe` and `@UsePipes`) +Pipes are used to transform or validate input arguments before they reach the tool handler method. + +### Interface: +```typescript +import { ArgumentMetadata } from '@nitrostack/core'; + +export interface PipeInterface { + transform(value: T, metadata: ArgumentMetadata): R | Promise; +} +``` + +### Example: +```typescript +import { Pipe, PipeInterface, ArgumentMetadata } from '@nitrostack/core'; + +@Pipe() +export class TrimPipe implements PipeInterface, Record> { + transform(value: Record, metadata: ArgumentMetadata) { + const trimmed: Record = {}; + for (const [key, val] of Object.entries(value)) { + trimmed[key] = typeof val === 'string' ? val.trim() : val; + } + return trimmed; + } +} +``` + +Apply the pipe using `@UsePipes(...)` on a tool method: +```typescript +import { Tool, UsePipes, z } from '@nitrostack/core'; +import { TrimPipe } from './trim.pipe.js'; + +export class MessagingTools { + @Tool({ + name: 'send_message', + description: 'Send a message to other stations.', + inputSchema: z.object({ text: z.string() }), + }) + @UsePipes(TrimPipe) + async sendMessage(input: { text: string }) { + return { sentText: input.text }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/tools-resources-prompts/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/tools-resources-prompts/SKILL.md @@ -0,0 +1,287 @@ +--- +name: nitrostack-tools-resources-prompts +description: Guidelines and patterns for defining Tools, Resources, and Prompts in a NitroStack application with schema validation via Zod, including caching, rate-limiting, and base64 file uploads. +--- + +## When to Use +Use this skill whenever you are defining, editing, or validating tools, resources, or prompts on a NitroStack MCP server. + +## Defining Tools with `@Tool` +An MCP tool exposes a function that an AI client can invoke. Decorate a service or controller method with `@Tool`. + +### Key Tool Options: +* `name`: Kebab-case or snake_case unique identifier. +* `description`: Detailed description explaining when and how the client should use it. +* `inputSchema`: A Zod object schema for strict validation of inputs. +* `outputSchema` (optional): Zod schema validating the output structure. + +```typescript +import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core'; + +@Controller('weather') +export class WeatherService { + @Tool({ + name: 'get_current_weather', + description: 'Get the current weather forecast for a specific city.', + inputSchema: z.object({ + city: z.string().describe('The name of the city, e.g., San Francisco'), + unit: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }) + @InitialTool() // Auto-invoked when the AI client initializes/starts + async getWeather( + input: { city: string; unit: 'celsius' | 'fahrenheit' }, + ctx: ExecutionContext + ) { + ctx.logger.info(`Fetching weather for ${input.city}`); + // implementation + return { + city: input.city, + temp: 22, + condition: 'Sunny', + }; + } +} +``` + +## Defining Resources with `@Resource` +An MCP resource exposes static or dynamic data files/URIs that the AI client can read. + +### Key Resource Options: +* `uri`: URI pattern (e.g., `git://{owner}/{repo}/file` or static `app://config`). +* `name`: Unique name of the resource. +* `description`: Explanation of what data this resource provides. +* `mimeType`: Mime type of the response (e.g., `text/plain`, `application/json`). + +```typescript +import { Resource, ExecutionContext } from '@nitrostack/core'; + +export class ConfigResources { + @Resource({ + uri: 'app://settings', + name: 'Application Settings', + description: 'System-wide configuration settings and parameters.', + mimeType: 'application/json', + }) + async getSettings(ctx: ExecutionContext) { + return { + environment: 'development', + debugMode: true, + }; + } +} +``` + +## Defining Prompts with `@Prompt` +An MCP prompt exposes reusable templates or instruction sets that guide LLMs. + +### Key Prompt Options: +* `name`: Name of the prompt. +* `description`: Describes what task this prompt helps accomplish. +* `arguments`: Declares parameters the client can supply to customize the prompt template. + +```typescript +import { Prompt, ExecutionContext } from '@nitrostack/core'; + +export class PromptTemplates { + @Prompt({ + name: 'code_review', + description: 'Provide an intensive code review for a given code snippet.', + arguments: [ + { name: 'language', description: 'The programming language, e.g., TypeScript', required: true }, + { name: 'code', description: 'The code snippet to review', required: true }, + ], + }) + async getCodeReviewPrompt( + args: { language: string; code: string }, + ctx: ExecutionContext + ) { + return { + messages: [ + { + role: 'user', + content: `You are an expert software engineer. Review this ${args.language} code:\n\n${args.code}`, + }, + ], + }; + } +} +``` + +--- + +## Tool Policies: Caching (`@Cache`) and Rate Limiting (`@RateLimit`) +You can control tool execution behaviors (such as performance optimization and throttling) using method decorators. + +### 1. Caching with `@Cache` +Use `@Cache` to cache tool execution outputs for a specified duration (TTL in seconds). This reduces database or API overhead for frequent identical requests. + +#### Options: +* `ttl`: Cache time-to-live in seconds (required). +* `key` (optional): Custom function `(input: any, context?: any) => string` that returns a unique cache key based on inputs. If not defined, a key is auto-generated from serialized input arguments. + +#### Example: +```typescript +import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core'; + +export class StationTools { + @Tool({ + name: 'get_system_status', + description: 'Fetch real-time station metrics. Response is cached.', + inputSchema: z.object({}), + }) + @Cache({ ttl: 60 }) // Caches status for 60 seconds + async getSystemStatus() { + return { temperature: 21.5, oxygen: 0.98 }; + } + + @Tool({ + name: 'get_crew_status', + description: 'Fetch status of a crew member. Cached by crew ID.', + inputSchema: z.object({ id: z.string() }), + }) + @Cache({ + ttl: 300, + key: (input) => `crew:status:${input.id}` + }) + async getCrewStatus(input: { id: string }) { + // ... + } +} +``` + +### 2. Rate Limiting with `@RateLimit` +Use `@RateLimit` to restrict the number of tool invocations within a specified time window to prevent client abuse. + +#### Options: +* `requests`: Number of allowed requests in the window (required). +* `window`: Throttling duration window (required). Supports formats like `'1s'`, `'1m'`, `'1h'`. +* `key` (optional): Custom function `(context: ExecutionContext) => string` to group rate limits. Useful for rate-limiting per user role or API key. + +#### Example: +```typescript +import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core'; + +export class DiagnosticTools { + @Tool({ + name: 'run_deep_diagnostic', + description: 'Run intensive diagnostics. Rate limited.', + inputSchema: z.object({}), + }) + @RateLimit({ requests: 3, window: '1m' }) // Max 3 requests per minute globally + async runDeepDiagnostic() { + return { diagnosticReport: 'All systems operational.' }; + } + + @Tool({ + name: 'request_supply_drop', + description: 'Request inventory supplies. Rate limited per user.', + inputSchema: z.object({ item: z.string() }), + }) + @RateLimit({ + requests: 5, + window: '1h', + key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous' + }) + async requestSupply(input: { item: string }, ctx: ExecutionContext) { + // ... + } +} +``` + +--- + +## Handling File Uploads in Tools +NitroStack supports file uploads from MCP clients (like NitroStudio) by passing the file as a base64-encoded string inside a tool's input parameters. + +### 1. Declaring Input Schema for File Uploads +To accept an uploaded file, define three Zod fields in your tool's `inputSchema`: +* `file_name`: The name of the file (e.g. `report.csv`). +* `file_type`: The MIME type (e.g. `text/csv`). +* `file_content`: The base64-encoded string containing the file data. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; + +export class FileTools { + @Tool({ + name: 'upload_document', + description: 'Upload a text document or image.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async uploadDocument(input: any, ctx: ExecutionContext) { + // Processing logic + } +} +``` + +### 2. Decoding Base64 Payloads +File uploads can arrive in two formats depending on the client: +1. **Data URL format**: `data:image/png;base64,iVBORw0KGgo...` +2. **Raw Base64 format**: `iVBORw0KGgo...` + +Use the following universal decoder pattern to parse either format into a Node `Buffer`: + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; + +function decodeBase64File(content: string): Buffer { + const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (matches && matches.length === 3) { + // Data URL format - decode matches[2] + return Buffer.from(matches[2], 'base64'); + } else { + // Raw base64 format - decode input directly + return Buffer.from(content, 'base64'); + } +} +``` + +### 3. Secure File Saving Example +Always validate the directory paths to prevent directory traversal attacks when saving files to disk. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); + +export class SecureUploadTools { + @Tool({ + name: 'save_uploaded_file', + description: 'Decodes and saves an uploaded file securely.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async saveFile(input: any, ctx: ExecutionContext) { + // Ensure uploads directory exists + if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + } + + // Secure destination path to prevent path traversal + const safeName = path.basename(input.file_name); + const filePath = path.join(UPLOAD_DIR, safeName); + if (!filePath.startsWith(UPLOAD_DIR)) { + throw new Error('Invalid file path detected (path traversal).'); + } + + // Decode and write to disk + const buffer = decodeBase64File(input.file_content); + fs.writeFileSync(filePath, buffer); + + ctx.logger.info(`Successfully saved file: ${safeName}`); + return { success: true, path: filePath }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/ui-widgets/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.copilot/skills/ui-widgets/SKILL.md @@ -0,0 +1,251 @@ +--- +name: nitrostack-ui-widgets +description: Best practices for linking tools to interactive frontend widgets using @Widget and @nitrostack/widgets SDK (including state sync, tool calling, display modes, media queries, and chat actions). +--- + +## When to Use +Use this skill when designing, building, or modifying interactive user interface widgets that display custom React content inside AI clients or NitroStudio. + +--- + +## 1. Backend Definition (`@Widget`) +To display a React-based widget for a tool's output, decorate the tool method with `@Widget`. + +### Options: +* **String Route**: A simple string representing the route identifier in the frontend React app (e.g. `'product-card'`). +* **Object Route**: Object including: + * `route` (required): The route path. + * `domain` (optional): Allowed sandbox domain. + * `csp` (optional): Content Security Policy guidelines. + +### Example: +```typescript +import { Tool, Widget, z } from '@nitrostack/core'; + +export class CatalogTools { + @Tool({ + name: 'fetch_product', + description: 'Get product information by barcode.', + inputSchema: z.object({ barcode: z.string() }), + }) + @Widget('product-details') // Maps to the "product-details" frontend component + async fetchProduct(input: { barcode: string }) { + return { + name: 'Super Nitro Energy Drink', + price: 2.99, + sku: input.barcode, + }; + } +} +``` + +--- + +## 2. Frontend React Widget (`@nitrostack/widgets`) +In your React widget frontend application (typically a Next.js client component), use the `useWidgetSDK` hook to receive input data from the client host. + +### React Component Example: +```tsx +'use client'; + +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +interface ProductData { + name: string; + price: number; + sku: string; +} + +export default function ProductDetailsWidget() { + const { isReady, getToolOutput, theme } = useWidgetSDK(); + const data = getToolOutput(); + + if (!isReady) { + return
Connecting to host...
; + } + + if (!data) { + return
No product data received.
; + } + + return ( +
+

{data.name}

+

${data.price.toFixed(2)}

+ SKU: {data.sku} +
+ ); +} +``` + +--- + +## 3. State Management & Synchronization (`useWidgetState`) +Use `useWidgetState` to manage and persist client-side widget state (e.g. selected tabs, filter values, input states). This state automatically synchronizes with the host application context, persisting it across page re-renders. + +### Example: +```tsx +import React from 'react'; +import { useWidgetState } from '@nitrostack/widgets'; + +export default function StationPanelWidget() { + const [state, setState] = useWidgetState(() => ({ + selectedTab: 'overview', + showExtendedInfo: false, + })); + + return ( +
+ +

Current Tab: {state?.selectedTab}

+
+ ); +} +``` + +--- + +## 4. Calling Core Tools from Widgets (`callTool`) +You can invoke other backend MCP tools directly from the frontend widget using `callTool`. This is useful for tool chaining or triggering detailed audits. + +### Example: +```tsx +import React, { useState } from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function SystemDiagnostics() { + const { callTool, isReady } = useWidgetSDK(); + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState(null); + + const runDiagnostic = async () => { + if (!isReady) return; + setIsRunning(true); + try { + const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' }); + setResult(response.result as string); + } catch (err) { + setResult('Diagnostic execution failed.'); + } finally { + setIsRunning(false); + } + }; + + return ( + + ); +} +``` + +--- + +## 5. Layout & Display Controls +Widgets can dynamically request size mode changes (fullscreen, inline, picture-in-picture) and adapt layouts to safe areas (dynamic islands/notches) or maximum height constraints. + +### Key Methods: +* `requestFullscreen()`: Switch host widget display to fullscreen. +* `requestInline()`: Switch host widget display back to inline. +* `requestPip()`: Float widget in Picture-in-Picture. +* `requestClose()`: Dismiss the widget completely. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function StatusBoard() { + const { + requestFullscreen, + requestInline, + requestClose, + displayMode, // Reactive property ('fullscreen' | 'inline' | 'pip') + maxHeight, // Reactive maxHeight constraint (in pixels) + getSafeArea // Insets data: { top, right, bottom, left } + } = useWidgetSDK(); + + const safeArea = getSafeArea() || { top: 0, bottom: 0 }; + + return ( +
+

Mode: {displayMode}

+ + + +
+ ); +} +``` + +--- + +## 6. Chat Navigation & Actions +Widgets can interact with the host chat pane using external browser links and follow-up prompts. + +### Key Methods: +* `openExternal(url)`: Open the target URL safely in the user's primary external browser. +* `sendFollowUpMessage(prompt)`: Insert a message into the chat flow, automatically submitting it to the LLM agent. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function MissionControl() { + const { openExternal, sendFollowUpMessage } = useWidgetSDK(); + + return ( +
+ {/* Open external documentation */} + + + {/* Ask LLM agent directly from the widget */} + +
+ ); +} +``` + +--- + +## 7. Media & Accessibility Queries +The SDK provides helper utilities to query target client capabilities for styling or accessibility. + +### Key Utilities: +* `prefersReducedMotion()`: Returns `true` if client settings specify reduced motion. Disable animations. +* `isPrimarilyTouchDevice()`: Returns `true` if the device has a coarse pointer (e.g. touch/mobile). Increase button target sizes. +* `isHoverAvailable()`: Returns `true` if pointer supports hover states. +* `prefersDarkColorScheme()`: Returns `true` if the system theme is dark. + +### Example: +```tsx +import React from 'react'; +import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets'; + +export default function AccessiblePanel() { + const isTouch = isPrimarilyTouchDevice(); + const reducedMotion = prefersReducedMotion(); + + return ( +
+ +
+ ); +} +``` + +--- + +## 8. Testing Widgets +* Open your project in **NitroStudio** for visual preview. +* Invoke the tool from the AI chat or testing pane to verify the widget updates instantly with the returned JSON structure. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/auth-security/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/auth-security/SKILL.md @@ -0,0 +1,141 @@ +--- +name: nitrostack-auth-security +description: Best practices for implementing JWT, API Keys, OAuth 2.1, and RBAC in a NitroStack application. +--- + +## When to Use +Use this skill when configuring security modules, implementing user authentication, restricting tool access via guards, or handling sensitive tokens. + +--- + +## 1. JSON Web Tokens (JWT) +To secure tools with JWT authentication: + +### Register `JWTModule`: +```typescript +import { JWTModule, Module, McpApp } from '@nitrostack/core'; + +@McpApp({ + server: { name: 'my-server', version: '1.0.0' } +}) +@Module({ + imports: [ + JWTModule.forRoot({ + secret: process.env.JWT_SECRET!, + expiresIn: '7d', + }), + ] +}) +export class AppModule {} +``` + +### Write a `JWTGuard`: +```typescript +import { Guard, ExecutionContext, Injectable, ConfigService } from '@nitrostack/core'; +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JWTGuard implements Guard { + constructor(private config: ConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const token = this.extractToken(context); + if (!token) return false; + + try { + const secret = this.config.get('JWT_SECRET'); + const payload = jwt.verify(token, secret) as any; + context.auth = { + subject: payload.sub, + role: payload.role, + token, + }; + return true; + } catch { + return false; + } + } + + private extractToken(context: ExecutionContext): string | null { + const auth = context.metadata?.authorization; + if (auth?.startsWith('Bearer ')) { + return auth.substring(7); + } + return null; + } +} +``` + +--- + +## 2. API Key Authentication +Use `ApiKeyModule` for service-to-service validation. + +### Register `ApiKeyModule`: +```typescript +import { ApiKeyModule, Module } from '@nitrostack/core'; + +@Module({ + imports: [ + ApiKeyModule.forRoot({ + keysEnvPrefix: 'API_KEY', // Reads API_KEY_1, API_KEY_2, etc. + headerName: 'x-api-key', + hashed: false, + }), + ] +}) +export class AppModule {} +``` + +### API Key Guard: +```typescript +import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core'; + +export class ApiKeyGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const apiKey = context.metadata?.['x-api-key'] || context.metadata?.apiKey; + if (!apiKey) return false; + + const isValid = await ApiKeyModule.validate(apiKey as string); + if (isValid) { + context.auth = { + subject: `apikey_${(apiKey as string).substring(0, 10)}`, + scopes: ['*'], + }; + return true; + } + return false; + } +} +``` + +--- + +## 3. Role-Based Access Control (RBAC) +Chain guards sequentially to implement user-role authorization. + +```typescript +import { Injectable, Guard, ExecutionContext, UseGuards, Tool, z } from '@nitrostack/core'; +import { JWTGuard } from './jwt.guard.js'; + +@Injectable() +export class AdminGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + // Requires JWTGuard to have populated context.auth first + return context.auth?.role === 'admin'; + } +} + +// Applying chained guards to a tool +export class SystemTools { + @Tool({ + name: 'reset_database', + description: 'Dangerous action: wipes database. Admin only.', + inputSchema: z.object({}), + }) + @UseGuards(JWTGuard, AdminGuard) // Chain auth first, then role check + async resetDatabase() { + return { success: true }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/mcp-app-architecture/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/mcp-app-architecture/SKILL.md @@ -0,0 +1,176 @@ +--- +name: nitrostack-mcp-app-architecture +description: Best practices and guidelines for bootstrapping, defining modules, using dependency injection, managing server lifecycles, and handling events in the NitroStack SDK. +--- + +## When to Use +Use this skill whenever you are bootstrapping a new NitroStack MCP server, creating modules, injecting services, or handling application lifecycle events. + +## Bootstrapping a NitroStack App +A NitroStack application is initialized with the `@McpApp` decorator on a root class, accompanied by a root `@Module`. + +```typescript +import { McpApp, Module } from '@nitrostack/core'; +import { DatabaseModule } from './database/database.module.js'; +import { UsersModule } from './users/users.module.js'; + +@McpApp({ + module: AppModule, + server: { + name: 'user-management-server', + version: '1.0.0', + }, +}) +@Module({ + imports: [DatabaseModule, UsersModule], +}) +export class AppModule {} +``` + +## Modules +Modules organize your application structure. Use the `@Module` decorator to define imports, exports, and providers. + +* **`imports`**: Other modules whose exported providers should be available in this module. +* **`providers`**: Services, tools, resources, or prompts that should be instantiated and managed by the DI container within this module. +* **`exports`**: Providers defined in this module that should be visible to other modules importing this one. + +```typescript +import { Module } from '@nitrostack/core'; +import { UsersService } from './users.service.js'; +import { UsersTools } from './users.tools.js'; + +@Module({ + providers: [UsersService, UsersTools], + exports: [UsersService], +}) + +## Controllers +Use the `@ControllerDecorator` (or alias it as `@Controller`) to group tools, resources, and prompts together. Controllers are automatically registered as singletons in the DI container. + +### Key Controller Options: +* **`prefix`**: A string prefix applied to every `@Tool` defined in this controller. For example, `@ControllerDecorator('github')` prefixing a tool named `create_issue` exposes it to MCP clients as `github_create_issue`. + +```typescript +import { ControllerDecorator as Controller, Tool, ExecutionContext } from '@nitrostack/core'; + +@Controller('github') +export class GitHubController { + @Tool({ + name: 'create_issue', + description: 'Create an issue in a repository', + inputSchema: z.object({ /* ... */ }) + }) + async createIssue(input: any, ctx: ExecutionContext) { + // Exposed to clients as "github_create_issue" + } +} +``` + +## Dependency Injection (DI) +NitroStack uses a robust dependency injection container to manage class instances and lifecycles. + +### Injection Lifecycles +1. **Singleton (Default)**: A single instance is shared across the entire application. +2. **Transient**: A new instance is created every time it is resolved/injected. +3. **Scoped**: A new instance is created per incoming request or context. + +```typescript +import { Injectable, Scope } from '@nitrostack/core'; + +@Injectable({ scope: Scope.SINGLETON }) +export class UsersService { + constructor(private readonly db: DatabaseService) {} + + async getUser(id: string) { + return this.db.query('SELECT * FROM users WHERE id = $1', [id]); + } +} +``` + +## Lifecycles and Hooks +Implement NestJS-style lifecycle interfaces on modules, controllers, or providers to hook into application state changes: + +* **`OnModuleInit`** (`onModuleInit`): Called after modules have initialized but before the server starts listening. +* **`OnApplicationBootstrap`** (`onApplicationBootstrap`): Called once the server is fully started and listening. +* **`OnModuleDestroy`** (`onModuleDestroy`): Called when the module or application is shutting down. +* **`BeforeApplicationShutdown`** (`beforeApplicationShutdown(signal?: string)`): Called before the application starts shutting down. Receives the OS signal (e.g. `SIGINT`). +* **`OnApplicationShutdown`** (`onApplicationShutdown(signal?: string)`): Called during shutdown. Receives the OS signal. + +```typescript +import { + Injectable, + OnModuleInit, + OnApplicationBootstrap, + OnModuleDestroy, + BeforeApplicationShutdown, + OnApplicationShutdown +} from '@nitrostack/core'; + +@Injectable() +export class DatabaseService + implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown +{ + async onModuleInit() { + await this.connect(); + } + + async onApplicationBootstrap() { + console.log('App ready to handle connections.'); + } + + async onModuleDestroy() { + await this.cleanupPendingQueries(); + } + + async beforeApplicationShutdown(signal?: string) { + console.log(`Shutting down soon (signal: ${signal}).`); + } + + async onApplicationShutdown(signal?: string) { + await this.disconnect(); + } +} +``` + +--- + +## Eventing System (`emitEvent` and `@OnEvent`) +NitroStack includes an internal eventing system to decouple components. A service or tool can emit an event using `emitEvent`, and any injectable class (like a handler service or controller) can subscribe using the `@OnEvent` decorator. + +### 1. Emitting Events +Call `emitEvent` to dispatch an event payload asynchronously. + +```typescript +import { Injectable, emitEvent } from '@nitrostack/core'; + +@Injectable() +export class SpaceShipService { + async launchShip(shipId: string) { + // Process launch... + + // Dispatch event + emitEvent('ship.launched', { + shipId, + timestamp: new Date().toISOString(), + }); + } +} +``` + +### 2. Listening to Events +Decorate a method inside any `@Injectable()` class with `@OnEvent('event_pattern')` to register it as an event handler. + +```typescript +import { Injectable, OnEvent } from '@nitrostack/core'; + +@Injectable({ deps: [] }) +export class FlightLogHandler { + @OnEvent('ship.launched') + async logLaunch(data: { shipId: string; timestamp: string }) { + console.error(`🚀 [EVENT] Ship ${data.shipId} was successfully launched at ${data.timestamp}`); + } +} +``` + +> [!NOTE] +> For the `@OnEvent` decorator to register properly, the containing class must be declared as a provider inside an active module. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/middleware-pipeline/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/middleware-pipeline/SKILL.md @@ -0,0 +1,235 @@ +--- +name: nitrostack-middleware-pipeline +description: Best practices for implementing and applying Guards, Interceptors, Middleware, Pipes, and Exception Filters in the NitroStack SDK. +--- + +## When to Use +Use this skill when implementing request validation, authorization checks, response mapping, logging, error handling, or performance tracking on NitroStack tool methods. + +--- + +## 1. Guards (`Guard` and `@UseGuards`) +Guards determine if a request should be processed by a tool handler based on authentication, authorization, or other conditions. + +### Interface: +```typescript +import { Guard, ExecutionContext } from '@nitrostack/core'; + +export interface Guard { + canActivate(context: ExecutionContext): boolean | Promise; +} +``` + +### Example: +```typescript +import { Guard, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class RolesGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const userRoles = context.clientMetadata?.roles || []; + return userRoles.includes('admin'); + } +} +``` + +Apply the guard using `@UseGuards(...)`: +```typescript +import { Tool, UseGuards, z } from '@nitrostack/core'; +import { RolesGuard } from './roles.guard.js'; + +export class AdminTools { + @Tool({ + name: 'delete_system_logs', + description: 'Delete all system logs from the server.', + inputSchema: z.object({}), + }) + @UseGuards(RolesGuard) + async deleteLogs() { + return { success: true }; + } +} +``` + +--- + +## 2. Interceptors (`InterceptorInterface` and `@UseInterceptors`) +Interceptors can transform/intercept input arguments or mapped output from a tool method execution. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface InterceptorInterface { + intercept(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { InterceptorInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class TimingInterceptor implements InterceptorInterface { + async intercept(context: ExecutionContext, next: () => Promise): Promise { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + context.logger.info(`Execution took ${duration}ms`); + return { + ...result, + _meta: { durationMs: duration } + }; + } +} +``` + +--- + +## 3. Exception Filters (`ExceptionFilterInterface` and `@UseFilters`) +Exception filters catch any errors thrown within guards, interceptors, or the tool handlers themselves, mapping them into user-friendly JSON payloads. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext): unknown | Promise; +} +``` + +### Example: +```typescript +import { ExceptionFilterInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class CustomExceptionFilter implements ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext) { + const message = exception instanceof Error ? exception.message : 'Unknown error'; + return { + error: true, + message, + timestamp: new Date().toISOString() + }; + } +} +``` + +Apply the filter using `@UseFilters(...)` on a tool method: + +```typescript +import { Tool, UseFilters, z } from '@nitrostack/core'; +import { CustomExceptionFilter } from './custom-exception.filter.js'; + +export class LoggingTools { + @Tool({ + name: 'generate_report', + description: 'Generates system usage reports.', + inputSchema: z.object({}), + }) + @UseFilters(CustomExceptionFilter) + async generateReport() { + throw new Error('Report generation is not implemented yet.'); + } +} +``` + +--- + +## 4. Middleware (`MiddlewareInterface`, `@Middleware` and `@UseMiddleware`) +Middleware executes before the request reaches the tool handler, and can wrap the handler execution by invoking `next()`. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface MiddlewareInterface { + use(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { Middleware, MiddlewareInterface, ExecutionContext } from '@nitrostack/core'; + +@Middleware() +export class LoggingMiddleware implements MiddlewareInterface { + async use(context: ExecutionContext, next: () => Promise): Promise { + context.logger.info(`Entering tool: ${context.toolName}`); + try { + const result = await next(); + context.logger.info(`Exiting tool: ${context.toolName}`); + return result; + } catch (error) { + context.logger.error(`Error in tool: ${error}`); + throw error; + } + } +} +``` + +Apply the middleware using `@UseMiddleware(...)` on a tool method: +```typescript +import { Tool, UseMiddleware, z } from '@nitrostack/core'; +import { LoggingMiddleware } from './logging.middleware.js'; + +export class StationTools { + @Tool({ + name: 'fetch_logs', + description: 'Fetch station operations logs.', + inputSchema: z.object({}), + }) + @UseMiddleware(LoggingMiddleware) + async fetchLogs() { + return { status: 'operational' }; + } +} +``` + +--- + +## 5. Pipes (`PipeInterface`, `@Pipe` and `@UsePipes`) +Pipes are used to transform or validate input arguments before they reach the tool handler method. + +### Interface: +```typescript +import { ArgumentMetadata } from '@nitrostack/core'; + +export interface PipeInterface { + transform(value: T, metadata: ArgumentMetadata): R | Promise; +} +``` + +### Example: +```typescript +import { Pipe, PipeInterface, ArgumentMetadata } from '@nitrostack/core'; + +@Pipe() +export class TrimPipe implements PipeInterface, Record> { + transform(value: Record, metadata: ArgumentMetadata) { + const trimmed: Record = {}; + for (const [key, val] of Object.entries(value)) { + trimmed[key] = typeof val === 'string' ? val.trim() : val; + } + return trimmed; + } +} +``` + +Apply the pipe using `@UsePipes(...)` on a tool method: +```typescript +import { Tool, UsePipes, z } from '@nitrostack/core'; +import { TrimPipe } from './trim.pipe.js'; + +export class MessagingTools { + @Tool({ + name: 'send_message', + description: 'Send a message to other stations.', + inputSchema: z.object({ text: z.string() }), + }) + @UsePipes(TrimPipe) + async sendMessage(input: { text: string }) { + return { sentText: input.text }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/tools-resources-prompts/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/tools-resources-prompts/SKILL.md @@ -0,0 +1,287 @@ +--- +name: nitrostack-tools-resources-prompts +description: Guidelines and patterns for defining Tools, Resources, and Prompts in a NitroStack application with schema validation via Zod, including caching, rate-limiting, and base64 file uploads. +--- + +## When to Use +Use this skill whenever you are defining, editing, or validating tools, resources, or prompts on a NitroStack MCP server. + +## Defining Tools with `@Tool` +An MCP tool exposes a function that an AI client can invoke. Decorate a service or controller method with `@Tool`. + +### Key Tool Options: +* `name`: Kebab-case or snake_case unique identifier. +* `description`: Detailed description explaining when and how the client should use it. +* `inputSchema`: A Zod object schema for strict validation of inputs. +* `outputSchema` (optional): Zod schema validating the output structure. + +```typescript +import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core'; + +@Controller('weather') +export class WeatherService { + @Tool({ + name: 'get_current_weather', + description: 'Get the current weather forecast for a specific city.', + inputSchema: z.object({ + city: z.string().describe('The name of the city, e.g., San Francisco'), + unit: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }) + @InitialTool() // Auto-invoked when the AI client initializes/starts + async getWeather( + input: { city: string; unit: 'celsius' | 'fahrenheit' }, + ctx: ExecutionContext + ) { + ctx.logger.info(`Fetching weather for ${input.city}`); + // implementation + return { + city: input.city, + temp: 22, + condition: 'Sunny', + }; + } +} +``` + +## Defining Resources with `@Resource` +An MCP resource exposes static or dynamic data files/URIs that the AI client can read. + +### Key Resource Options: +* `uri`: URI pattern (e.g., `git://{owner}/{repo}/file` or static `app://config`). +* `name`: Unique name of the resource. +* `description`: Explanation of what data this resource provides. +* `mimeType`: Mime type of the response (e.g., `text/plain`, `application/json`). + +```typescript +import { Resource, ExecutionContext } from '@nitrostack/core'; + +export class ConfigResources { + @Resource({ + uri: 'app://settings', + name: 'Application Settings', + description: 'System-wide configuration settings and parameters.', + mimeType: 'application/json', + }) + async getSettings(ctx: ExecutionContext) { + return { + environment: 'development', + debugMode: true, + }; + } +} +``` + +## Defining Prompts with `@Prompt` +An MCP prompt exposes reusable templates or instruction sets that guide LLMs. + +### Key Prompt Options: +* `name`: Name of the prompt. +* `description`: Describes what task this prompt helps accomplish. +* `arguments`: Declares parameters the client can supply to customize the prompt template. + +```typescript +import { Prompt, ExecutionContext } from '@nitrostack/core'; + +export class PromptTemplates { + @Prompt({ + name: 'code_review', + description: 'Provide an intensive code review for a given code snippet.', + arguments: [ + { name: 'language', description: 'The programming language, e.g., TypeScript', required: true }, + { name: 'code', description: 'The code snippet to review', required: true }, + ], + }) + async getCodeReviewPrompt( + args: { language: string; code: string }, + ctx: ExecutionContext + ) { + return { + messages: [ + { + role: 'user', + content: `You are an expert software engineer. Review this ${args.language} code:\n\n${args.code}`, + }, + ], + }; + } +} +``` + +--- + +## Tool Policies: Caching (`@Cache`) and Rate Limiting (`@RateLimit`) +You can control tool execution behaviors (such as performance optimization and throttling) using method decorators. + +### 1. Caching with `@Cache` +Use `@Cache` to cache tool execution outputs for a specified duration (TTL in seconds). This reduces database or API overhead for frequent identical requests. + +#### Options: +* `ttl`: Cache time-to-live in seconds (required). +* `key` (optional): Custom function `(input: any, context?: any) => string` that returns a unique cache key based on inputs. If not defined, a key is auto-generated from serialized input arguments. + +#### Example: +```typescript +import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core'; + +export class StationTools { + @Tool({ + name: 'get_system_status', + description: 'Fetch real-time station metrics. Response is cached.', + inputSchema: z.object({}), + }) + @Cache({ ttl: 60 }) // Caches status for 60 seconds + async getSystemStatus() { + return { temperature: 21.5, oxygen: 0.98 }; + } + + @Tool({ + name: 'get_crew_status', + description: 'Fetch status of a crew member. Cached by crew ID.', + inputSchema: z.object({ id: z.string() }), + }) + @Cache({ + ttl: 300, + key: (input) => `crew:status:${input.id}` + }) + async getCrewStatus(input: { id: string }) { + // ... + } +} +``` + +### 2. Rate Limiting with `@RateLimit` +Use `@RateLimit` to restrict the number of tool invocations within a specified time window to prevent client abuse. + +#### Options: +* `requests`: Number of allowed requests in the window (required). +* `window`: Throttling duration window (required). Supports formats like `'1s'`, `'1m'`, `'1h'`. +* `key` (optional): Custom function `(context: ExecutionContext) => string` to group rate limits. Useful for rate-limiting per user role or API key. + +#### Example: +```typescript +import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core'; + +export class DiagnosticTools { + @Tool({ + name: 'run_deep_diagnostic', + description: 'Run intensive diagnostics. Rate limited.', + inputSchema: z.object({}), + }) + @RateLimit({ requests: 3, window: '1m' }) // Max 3 requests per minute globally + async runDeepDiagnostic() { + return { diagnosticReport: 'All systems operational.' }; + } + + @Tool({ + name: 'request_supply_drop', + description: 'Request inventory supplies. Rate limited per user.', + inputSchema: z.object({ item: z.string() }), + }) + @RateLimit({ + requests: 5, + window: '1h', + key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous' + }) + async requestSupply(input: { item: string }, ctx: ExecutionContext) { + // ... + } +} +``` + +--- + +## Handling File Uploads in Tools +NitroStack supports file uploads from MCP clients (like NitroStudio) by passing the file as a base64-encoded string inside a tool's input parameters. + +### 1. Declaring Input Schema for File Uploads +To accept an uploaded file, define three Zod fields in your tool's `inputSchema`: +* `file_name`: The name of the file (e.g. `report.csv`). +* `file_type`: The MIME type (e.g. `text/csv`). +* `file_content`: The base64-encoded string containing the file data. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; + +export class FileTools { + @Tool({ + name: 'upload_document', + description: 'Upload a text document or image.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async uploadDocument(input: any, ctx: ExecutionContext) { + // Processing logic + } +} +``` + +### 2. Decoding Base64 Payloads +File uploads can arrive in two formats depending on the client: +1. **Data URL format**: `data:image/png;base64,iVBORw0KGgo...` +2. **Raw Base64 format**: `iVBORw0KGgo...` + +Use the following universal decoder pattern to parse either format into a Node `Buffer`: + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; + +function decodeBase64File(content: string): Buffer { + const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (matches && matches.length === 3) { + // Data URL format - decode matches[2] + return Buffer.from(matches[2], 'base64'); + } else { + // Raw base64 format - decode input directly + return Buffer.from(content, 'base64'); + } +} +``` + +### 3. Secure File Saving Example +Always validate the directory paths to prevent directory traversal attacks when saving files to disk. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); + +export class SecureUploadTools { + @Tool({ + name: 'save_uploaded_file', + description: 'Decodes and saves an uploaded file securely.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async saveFile(input: any, ctx: ExecutionContext) { + // Ensure uploads directory exists + if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + } + + // Secure destination path to prevent path traversal + const safeName = path.basename(input.file_name); + const filePath = path.join(UPLOAD_DIR, safeName); + if (!filePath.startsWith(UPLOAD_DIR)) { + throw new Error('Invalid file path detected (path traversal).'); + } + + // Decode and write to disk + const buffer = decodeBase64File(input.file_content); + fs.writeFileSync(filePath, buffer); + + ctx.logger.info(`Successfully saved file: ${safeName}`); + return { success: true, path: filePath }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/ui-widgets/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.cursor/skills/ui-widgets/SKILL.md @@ -0,0 +1,251 @@ +--- +name: nitrostack-ui-widgets +description: Best practices for linking tools to interactive frontend widgets using @Widget and @nitrostack/widgets SDK (including state sync, tool calling, display modes, media queries, and chat actions). +--- + +## When to Use +Use this skill when designing, building, or modifying interactive user interface widgets that display custom React content inside AI clients or NitroStudio. + +--- + +## 1. Backend Definition (`@Widget`) +To display a React-based widget for a tool's output, decorate the tool method with `@Widget`. + +### Options: +* **String Route**: A simple string representing the route identifier in the frontend React app (e.g. `'product-card'`). +* **Object Route**: Object including: + * `route` (required): The route path. + * `domain` (optional): Allowed sandbox domain. + * `csp` (optional): Content Security Policy guidelines. + +### Example: +```typescript +import { Tool, Widget, z } from '@nitrostack/core'; + +export class CatalogTools { + @Tool({ + name: 'fetch_product', + description: 'Get product information by barcode.', + inputSchema: z.object({ barcode: z.string() }), + }) + @Widget('product-details') // Maps to the "product-details" frontend component + async fetchProduct(input: { barcode: string }) { + return { + name: 'Super Nitro Energy Drink', + price: 2.99, + sku: input.barcode, + }; + } +} +``` + +--- + +## 2. Frontend React Widget (`@nitrostack/widgets`) +In your React widget frontend application (typically a Next.js client component), use the `useWidgetSDK` hook to receive input data from the client host. + +### React Component Example: +```tsx +'use client'; + +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +interface ProductData { + name: string; + price: number; + sku: string; +} + +export default function ProductDetailsWidget() { + const { isReady, getToolOutput, theme } = useWidgetSDK(); + const data = getToolOutput(); + + if (!isReady) { + return
Connecting to host...
; + } + + if (!data) { + return
No product data received.
; + } + + return ( +
+

{data.name}

+

${data.price.toFixed(2)}

+ SKU: {data.sku} +
+ ); +} +``` + +--- + +## 3. State Management & Synchronization (`useWidgetState`) +Use `useWidgetState` to manage and persist client-side widget state (e.g. selected tabs, filter values, input states). This state automatically synchronizes with the host application context, persisting it across page re-renders. + +### Example: +```tsx +import React from 'react'; +import { useWidgetState } from '@nitrostack/widgets'; + +export default function StationPanelWidget() { + const [state, setState] = useWidgetState(() => ({ + selectedTab: 'overview', + showExtendedInfo: false, + })); + + return ( +
+ +

Current Tab: {state?.selectedTab}

+
+ ); +} +``` + +--- + +## 4. Calling Core Tools from Widgets (`callTool`) +You can invoke other backend MCP tools directly from the frontend widget using `callTool`. This is useful for tool chaining or triggering detailed audits. + +### Example: +```tsx +import React, { useState } from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function SystemDiagnostics() { + const { callTool, isReady } = useWidgetSDK(); + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState(null); + + const runDiagnostic = async () => { + if (!isReady) return; + setIsRunning(true); + try { + const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' }); + setResult(response.result as string); + } catch (err) { + setResult('Diagnostic execution failed.'); + } finally { + setIsRunning(false); + } + }; + + return ( + + ); +} +``` + +--- + +## 5. Layout & Display Controls +Widgets can dynamically request size mode changes (fullscreen, inline, picture-in-picture) and adapt layouts to safe areas (dynamic islands/notches) or maximum height constraints. + +### Key Methods: +* `requestFullscreen()`: Switch host widget display to fullscreen. +* `requestInline()`: Switch host widget display back to inline. +* `requestPip()`: Float widget in Picture-in-Picture. +* `requestClose()`: Dismiss the widget completely. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function StatusBoard() { + const { + requestFullscreen, + requestInline, + requestClose, + displayMode, // Reactive property ('fullscreen' | 'inline' | 'pip') + maxHeight, // Reactive maxHeight constraint (in pixels) + getSafeArea // Insets data: { top, right, bottom, left } + } = useWidgetSDK(); + + const safeArea = getSafeArea() || { top: 0, bottom: 0 }; + + return ( +
+

Mode: {displayMode}

+ + + +
+ ); +} +``` + +--- + +## 6. Chat Navigation & Actions +Widgets can interact with the host chat pane using external browser links and follow-up prompts. + +### Key Methods: +* `openExternal(url)`: Open the target URL safely in the user's primary external browser. +* `sendFollowUpMessage(prompt)`: Insert a message into the chat flow, automatically submitting it to the LLM agent. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function MissionControl() { + const { openExternal, sendFollowUpMessage } = useWidgetSDK(); + + return ( +
+ {/* Open external documentation */} + + + {/* Ask LLM agent directly from the widget */} + +
+ ); +} +``` + +--- + +## 7. Media & Accessibility Queries +The SDK provides helper utilities to query target client capabilities for styling or accessibility. + +### Key Utilities: +* `prefersReducedMotion()`: Returns `true` if client settings specify reduced motion. Disable animations. +* `isPrimarilyTouchDevice()`: Returns `true` if the device has a coarse pointer (e.g. touch/mobile). Increase button target sizes. +* `isHoverAvailable()`: Returns `true` if pointer supports hover states. +* `prefersDarkColorScheme()`: Returns `true` if the system theme is dark. + +### Example: +```tsx +import React from 'react'; +import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets'; + +export default function AccessiblePanel() { + const isTouch = isPrimarilyTouchDevice(); + const reducedMotion = prefersReducedMotion(); + + return ( +
+ +
+ ); +} +``` + +--- + +## 8. Testing Widgets +* Open your project in **NitroStudio** for visual preview. +* Invoke the tool from the AI chat or testing pane to verify the widget updates instantly with the returned JSON structure. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.env.example b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.env.example new file mode 100644 index 000000000..4628cbff6 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.env.example @@ -0,0 +1,13 @@ +# NitroStack Configuration +NITRO_LOG_LEVEL=info +NITROSTACK_APP_MODE=universal + +# Server Transport Configuration (Optional) +# ============================================================================= +# MCP_TRANSPORT_TYPE: Toggles transport mode. Values: stdio | http | dual. +# Defaults to 'stdio' in development and 'dual' in production/NODE_ENV=production. +# ============================================================================= +# MCP_TRANSPORT_TYPE=stdio +# PORT=3000 +# HOST=localhost +# ENABLE_CORS=true diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/auth-security/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/auth-security/SKILL.md @@ -0,0 +1,141 @@ +--- +name: nitrostack-auth-security +description: Best practices for implementing JWT, API Keys, OAuth 2.1, and RBAC in a NitroStack application. +--- + +## When to Use +Use this skill when configuring security modules, implementing user authentication, restricting tool access via guards, or handling sensitive tokens. + +--- + +## 1. JSON Web Tokens (JWT) +To secure tools with JWT authentication: + +### Register `JWTModule`: +```typescript +import { JWTModule, Module, McpApp } from '@nitrostack/core'; + +@McpApp({ + server: { name: 'my-server', version: '1.0.0' } +}) +@Module({ + imports: [ + JWTModule.forRoot({ + secret: process.env.JWT_SECRET!, + expiresIn: '7d', + }), + ] +}) +export class AppModule {} +``` + +### Write a `JWTGuard`: +```typescript +import { Guard, ExecutionContext, Injectable, ConfigService } from '@nitrostack/core'; +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JWTGuard implements Guard { + constructor(private config: ConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const token = this.extractToken(context); + if (!token) return false; + + try { + const secret = this.config.get('JWT_SECRET'); + const payload = jwt.verify(token, secret) as any; + context.auth = { + subject: payload.sub, + role: payload.role, + token, + }; + return true; + } catch { + return false; + } + } + + private extractToken(context: ExecutionContext): string | null { + const auth = context.metadata?.authorization; + if (auth?.startsWith('Bearer ')) { + return auth.substring(7); + } + return null; + } +} +``` + +--- + +## 2. API Key Authentication +Use `ApiKeyModule` for service-to-service validation. + +### Register `ApiKeyModule`: +```typescript +import { ApiKeyModule, Module } from '@nitrostack/core'; + +@Module({ + imports: [ + ApiKeyModule.forRoot({ + keysEnvPrefix: 'API_KEY', // Reads API_KEY_1, API_KEY_2, etc. + headerName: 'x-api-key', + hashed: false, + }), + ] +}) +export class AppModule {} +``` + +### API Key Guard: +```typescript +import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core'; + +export class ApiKeyGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const apiKey = context.metadata?.['x-api-key'] || context.metadata?.apiKey; + if (!apiKey) return false; + + const isValid = await ApiKeyModule.validate(apiKey as string); + if (isValid) { + context.auth = { + subject: `apikey_${(apiKey as string).substring(0, 10)}`, + scopes: ['*'], + }; + return true; + } + return false; + } +} +``` + +--- + +## 3. Role-Based Access Control (RBAC) +Chain guards sequentially to implement user-role authorization. + +```typescript +import { Injectable, Guard, ExecutionContext, UseGuards, Tool, z } from '@nitrostack/core'; +import { JWTGuard } from './jwt.guard.js'; + +@Injectable() +export class AdminGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + // Requires JWTGuard to have populated context.auth first + return context.auth?.role === 'admin'; + } +} + +// Applying chained guards to a tool +export class SystemTools { + @Tool({ + name: 'reset_database', + description: 'Dangerous action: wipes database. Admin only.', + inputSchema: z.object({}), + }) + @UseGuards(JWTGuard, AdminGuard) // Chain auth first, then role check + async resetDatabase() { + return { success: true }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/mcp-app-architecture/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/mcp-app-architecture/SKILL.md @@ -0,0 +1,176 @@ +--- +name: nitrostack-mcp-app-architecture +description: Best practices and guidelines for bootstrapping, defining modules, using dependency injection, managing server lifecycles, and handling events in the NitroStack SDK. +--- + +## When to Use +Use this skill whenever you are bootstrapping a new NitroStack MCP server, creating modules, injecting services, or handling application lifecycle events. + +## Bootstrapping a NitroStack App +A NitroStack application is initialized with the `@McpApp` decorator on a root class, accompanied by a root `@Module`. + +```typescript +import { McpApp, Module } from '@nitrostack/core'; +import { DatabaseModule } from './database/database.module.js'; +import { UsersModule } from './users/users.module.js'; + +@McpApp({ + module: AppModule, + server: { + name: 'user-management-server', + version: '1.0.0', + }, +}) +@Module({ + imports: [DatabaseModule, UsersModule], +}) +export class AppModule {} +``` + +## Modules +Modules organize your application structure. Use the `@Module` decorator to define imports, exports, and providers. + +* **`imports`**: Other modules whose exported providers should be available in this module. +* **`providers`**: Services, tools, resources, or prompts that should be instantiated and managed by the DI container within this module. +* **`exports`**: Providers defined in this module that should be visible to other modules importing this one. + +```typescript +import { Module } from '@nitrostack/core'; +import { UsersService } from './users.service.js'; +import { UsersTools } from './users.tools.js'; + +@Module({ + providers: [UsersService, UsersTools], + exports: [UsersService], +}) + +## Controllers +Use the `@ControllerDecorator` (or alias it as `@Controller`) to group tools, resources, and prompts together. Controllers are automatically registered as singletons in the DI container. + +### Key Controller Options: +* **`prefix`**: A string prefix applied to every `@Tool` defined in this controller. For example, `@ControllerDecorator('github')` prefixing a tool named `create_issue` exposes it to MCP clients as `github_create_issue`. + +```typescript +import { ControllerDecorator as Controller, Tool, ExecutionContext } from '@nitrostack/core'; + +@Controller('github') +export class GitHubController { + @Tool({ + name: 'create_issue', + description: 'Create an issue in a repository', + inputSchema: z.object({ /* ... */ }) + }) + async createIssue(input: any, ctx: ExecutionContext) { + // Exposed to clients as "github_create_issue" + } +} +``` + +## Dependency Injection (DI) +NitroStack uses a robust dependency injection container to manage class instances and lifecycles. + +### Injection Lifecycles +1. **Singleton (Default)**: A single instance is shared across the entire application. +2. **Transient**: A new instance is created every time it is resolved/injected. +3. **Scoped**: A new instance is created per incoming request or context. + +```typescript +import { Injectable, Scope } from '@nitrostack/core'; + +@Injectable({ scope: Scope.SINGLETON }) +export class UsersService { + constructor(private readonly db: DatabaseService) {} + + async getUser(id: string) { + return this.db.query('SELECT * FROM users WHERE id = $1', [id]); + } +} +``` + +## Lifecycles and Hooks +Implement NestJS-style lifecycle interfaces on modules, controllers, or providers to hook into application state changes: + +* **`OnModuleInit`** (`onModuleInit`): Called after modules have initialized but before the server starts listening. +* **`OnApplicationBootstrap`** (`onApplicationBootstrap`): Called once the server is fully started and listening. +* **`OnModuleDestroy`** (`onModuleDestroy`): Called when the module or application is shutting down. +* **`BeforeApplicationShutdown`** (`beforeApplicationShutdown(signal?: string)`): Called before the application starts shutting down. Receives the OS signal (e.g. `SIGINT`). +* **`OnApplicationShutdown`** (`onApplicationShutdown(signal?: string)`): Called during shutdown. Receives the OS signal. + +```typescript +import { + Injectable, + OnModuleInit, + OnApplicationBootstrap, + OnModuleDestroy, + BeforeApplicationShutdown, + OnApplicationShutdown +} from '@nitrostack/core'; + +@Injectable() +export class DatabaseService + implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown +{ + async onModuleInit() { + await this.connect(); + } + + async onApplicationBootstrap() { + console.log('App ready to handle connections.'); + } + + async onModuleDestroy() { + await this.cleanupPendingQueries(); + } + + async beforeApplicationShutdown(signal?: string) { + console.log(`Shutting down soon (signal: ${signal}).`); + } + + async onApplicationShutdown(signal?: string) { + await this.disconnect(); + } +} +``` + +--- + +## Eventing System (`emitEvent` and `@OnEvent`) +NitroStack includes an internal eventing system to decouple components. A service or tool can emit an event using `emitEvent`, and any injectable class (like a handler service or controller) can subscribe using the `@OnEvent` decorator. + +### 1. Emitting Events +Call `emitEvent` to dispatch an event payload asynchronously. + +```typescript +import { Injectable, emitEvent } from '@nitrostack/core'; + +@Injectable() +export class SpaceShipService { + async launchShip(shipId: string) { + // Process launch... + + // Dispatch event + emitEvent('ship.launched', { + shipId, + timestamp: new Date().toISOString(), + }); + } +} +``` + +### 2. Listening to Events +Decorate a method inside any `@Injectable()` class with `@OnEvent('event_pattern')` to register it as an event handler. + +```typescript +import { Injectable, OnEvent } from '@nitrostack/core'; + +@Injectable({ deps: [] }) +export class FlightLogHandler { + @OnEvent('ship.launched') + async logLaunch(data: { shipId: string; timestamp: string }) { + console.error(`🚀 [EVENT] Ship ${data.shipId} was successfully launched at ${data.timestamp}`); + } +} +``` + +> [!NOTE] +> For the `@OnEvent` decorator to register properly, the containing class must be declared as a provider inside an active module. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/middleware-pipeline/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/middleware-pipeline/SKILL.md @@ -0,0 +1,235 @@ +--- +name: nitrostack-middleware-pipeline +description: Best practices for implementing and applying Guards, Interceptors, Middleware, Pipes, and Exception Filters in the NitroStack SDK. +--- + +## When to Use +Use this skill when implementing request validation, authorization checks, response mapping, logging, error handling, or performance tracking on NitroStack tool methods. + +--- + +## 1. Guards (`Guard` and `@UseGuards`) +Guards determine if a request should be processed by a tool handler based on authentication, authorization, or other conditions. + +### Interface: +```typescript +import { Guard, ExecutionContext } from '@nitrostack/core'; + +export interface Guard { + canActivate(context: ExecutionContext): boolean | Promise; +} +``` + +### Example: +```typescript +import { Guard, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class RolesGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const userRoles = context.clientMetadata?.roles || []; + return userRoles.includes('admin'); + } +} +``` + +Apply the guard using `@UseGuards(...)`: +```typescript +import { Tool, UseGuards, z } from '@nitrostack/core'; +import { RolesGuard } from './roles.guard.js'; + +export class AdminTools { + @Tool({ + name: 'delete_system_logs', + description: 'Delete all system logs from the server.', + inputSchema: z.object({}), + }) + @UseGuards(RolesGuard) + async deleteLogs() { + return { success: true }; + } +} +``` + +--- + +## 2. Interceptors (`InterceptorInterface` and `@UseInterceptors`) +Interceptors can transform/intercept input arguments or mapped output from a tool method execution. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface InterceptorInterface { + intercept(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { InterceptorInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class TimingInterceptor implements InterceptorInterface { + async intercept(context: ExecutionContext, next: () => Promise): Promise { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + context.logger.info(`Execution took ${duration}ms`); + return { + ...result, + _meta: { durationMs: duration } + }; + } +} +``` + +--- + +## 3. Exception Filters (`ExceptionFilterInterface` and `@UseFilters`) +Exception filters catch any errors thrown within guards, interceptors, or the tool handlers themselves, mapping them into user-friendly JSON payloads. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext): unknown | Promise; +} +``` + +### Example: +```typescript +import { ExceptionFilterInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class CustomExceptionFilter implements ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext) { + const message = exception instanceof Error ? exception.message : 'Unknown error'; + return { + error: true, + message, + timestamp: new Date().toISOString() + }; + } +} +``` + +Apply the filter using `@UseFilters(...)` on a tool method: + +```typescript +import { Tool, UseFilters, z } from '@nitrostack/core'; +import { CustomExceptionFilter } from './custom-exception.filter.js'; + +export class LoggingTools { + @Tool({ + name: 'generate_report', + description: 'Generates system usage reports.', + inputSchema: z.object({}), + }) + @UseFilters(CustomExceptionFilter) + async generateReport() { + throw new Error('Report generation is not implemented yet.'); + } +} +``` + +--- + +## 4. Middleware (`MiddlewareInterface`, `@Middleware` and `@UseMiddleware`) +Middleware executes before the request reaches the tool handler, and can wrap the handler execution by invoking `next()`. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface MiddlewareInterface { + use(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { Middleware, MiddlewareInterface, ExecutionContext } from '@nitrostack/core'; + +@Middleware() +export class LoggingMiddleware implements MiddlewareInterface { + async use(context: ExecutionContext, next: () => Promise): Promise { + context.logger.info(`Entering tool: ${context.toolName}`); + try { + const result = await next(); + context.logger.info(`Exiting tool: ${context.toolName}`); + return result; + } catch (error) { + context.logger.error(`Error in tool: ${error}`); + throw error; + } + } +} +``` + +Apply the middleware using `@UseMiddleware(...)` on a tool method: +```typescript +import { Tool, UseMiddleware, z } from '@nitrostack/core'; +import { LoggingMiddleware } from './logging.middleware.js'; + +export class StationTools { + @Tool({ + name: 'fetch_logs', + description: 'Fetch station operations logs.', + inputSchema: z.object({}), + }) + @UseMiddleware(LoggingMiddleware) + async fetchLogs() { + return { status: 'operational' }; + } +} +``` + +--- + +## 5. Pipes (`PipeInterface`, `@Pipe` and `@UsePipes`) +Pipes are used to transform or validate input arguments before they reach the tool handler method. + +### Interface: +```typescript +import { ArgumentMetadata } from '@nitrostack/core'; + +export interface PipeInterface { + transform(value: T, metadata: ArgumentMetadata): R | Promise; +} +``` + +### Example: +```typescript +import { Pipe, PipeInterface, ArgumentMetadata } from '@nitrostack/core'; + +@Pipe() +export class TrimPipe implements PipeInterface, Record> { + transform(value: Record, metadata: ArgumentMetadata) { + const trimmed: Record = {}; + for (const [key, val] of Object.entries(value)) { + trimmed[key] = typeof val === 'string' ? val.trim() : val; + } + return trimmed; + } +} +``` + +Apply the pipe using `@UsePipes(...)` on a tool method: +```typescript +import { Tool, UsePipes, z } from '@nitrostack/core'; +import { TrimPipe } from './trim.pipe.js'; + +export class MessagingTools { + @Tool({ + name: 'send_message', + description: 'Send a message to other stations.', + inputSchema: z.object({ text: z.string() }), + }) + @UsePipes(TrimPipe) + async sendMessage(input: { text: string }) { + return { sentText: input.text }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/tools-resources-prompts/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/tools-resources-prompts/SKILL.md @@ -0,0 +1,287 @@ +--- +name: nitrostack-tools-resources-prompts +description: Guidelines and patterns for defining Tools, Resources, and Prompts in a NitroStack application with schema validation via Zod, including caching, rate-limiting, and base64 file uploads. +--- + +## When to Use +Use this skill whenever you are defining, editing, or validating tools, resources, or prompts on a NitroStack MCP server. + +## Defining Tools with `@Tool` +An MCP tool exposes a function that an AI client can invoke. Decorate a service or controller method with `@Tool`. + +### Key Tool Options: +* `name`: Kebab-case or snake_case unique identifier. +* `description`: Detailed description explaining when and how the client should use it. +* `inputSchema`: A Zod object schema for strict validation of inputs. +* `outputSchema` (optional): Zod schema validating the output structure. + +```typescript +import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core'; + +@Controller('weather') +export class WeatherService { + @Tool({ + name: 'get_current_weather', + description: 'Get the current weather forecast for a specific city.', + inputSchema: z.object({ + city: z.string().describe('The name of the city, e.g., San Francisco'), + unit: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }) + @InitialTool() // Auto-invoked when the AI client initializes/starts + async getWeather( + input: { city: string; unit: 'celsius' | 'fahrenheit' }, + ctx: ExecutionContext + ) { + ctx.logger.info(`Fetching weather for ${input.city}`); + // implementation + return { + city: input.city, + temp: 22, + condition: 'Sunny', + }; + } +} +``` + +## Defining Resources with `@Resource` +An MCP resource exposes static or dynamic data files/URIs that the AI client can read. + +### Key Resource Options: +* `uri`: URI pattern (e.g., `git://{owner}/{repo}/file` or static `app://config`). +* `name`: Unique name of the resource. +* `description`: Explanation of what data this resource provides. +* `mimeType`: Mime type of the response (e.g., `text/plain`, `application/json`). + +```typescript +import { Resource, ExecutionContext } from '@nitrostack/core'; + +export class ConfigResources { + @Resource({ + uri: 'app://settings', + name: 'Application Settings', + description: 'System-wide configuration settings and parameters.', + mimeType: 'application/json', + }) + async getSettings(ctx: ExecutionContext) { + return { + environment: 'development', + debugMode: true, + }; + } +} +``` + +## Defining Prompts with `@Prompt` +An MCP prompt exposes reusable templates or instruction sets that guide LLMs. + +### Key Prompt Options: +* `name`: Name of the prompt. +* `description`: Describes what task this prompt helps accomplish. +* `arguments`: Declares parameters the client can supply to customize the prompt template. + +```typescript +import { Prompt, ExecutionContext } from '@nitrostack/core'; + +export class PromptTemplates { + @Prompt({ + name: 'code_review', + description: 'Provide an intensive code review for a given code snippet.', + arguments: [ + { name: 'language', description: 'The programming language, e.g., TypeScript', required: true }, + { name: 'code', description: 'The code snippet to review', required: true }, + ], + }) + async getCodeReviewPrompt( + args: { language: string; code: string }, + ctx: ExecutionContext + ) { + return { + messages: [ + { + role: 'user', + content: `You are an expert software engineer. Review this ${args.language} code:\n\n${args.code}`, + }, + ], + }; + } +} +``` + +--- + +## Tool Policies: Caching (`@Cache`) and Rate Limiting (`@RateLimit`) +You can control tool execution behaviors (such as performance optimization and throttling) using method decorators. + +### 1. Caching with `@Cache` +Use `@Cache` to cache tool execution outputs for a specified duration (TTL in seconds). This reduces database or API overhead for frequent identical requests. + +#### Options: +* `ttl`: Cache time-to-live in seconds (required). +* `key` (optional): Custom function `(input: any, context?: any) => string` that returns a unique cache key based on inputs. If not defined, a key is auto-generated from serialized input arguments. + +#### Example: +```typescript +import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core'; + +export class StationTools { + @Tool({ + name: 'get_system_status', + description: 'Fetch real-time station metrics. Response is cached.', + inputSchema: z.object({}), + }) + @Cache({ ttl: 60 }) // Caches status for 60 seconds + async getSystemStatus() { + return { temperature: 21.5, oxygen: 0.98 }; + } + + @Tool({ + name: 'get_crew_status', + description: 'Fetch status of a crew member. Cached by crew ID.', + inputSchema: z.object({ id: z.string() }), + }) + @Cache({ + ttl: 300, + key: (input) => `crew:status:${input.id}` + }) + async getCrewStatus(input: { id: string }) { + // ... + } +} +``` + +### 2. Rate Limiting with `@RateLimit` +Use `@RateLimit` to restrict the number of tool invocations within a specified time window to prevent client abuse. + +#### Options: +* `requests`: Number of allowed requests in the window (required). +* `window`: Throttling duration window (required). Supports formats like `'1s'`, `'1m'`, `'1h'`. +* `key` (optional): Custom function `(context: ExecutionContext) => string` to group rate limits. Useful for rate-limiting per user role or API key. + +#### Example: +```typescript +import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core'; + +export class DiagnosticTools { + @Tool({ + name: 'run_deep_diagnostic', + description: 'Run intensive diagnostics. Rate limited.', + inputSchema: z.object({}), + }) + @RateLimit({ requests: 3, window: '1m' }) // Max 3 requests per minute globally + async runDeepDiagnostic() { + return { diagnosticReport: 'All systems operational.' }; + } + + @Tool({ + name: 'request_supply_drop', + description: 'Request inventory supplies. Rate limited per user.', + inputSchema: z.object({ item: z.string() }), + }) + @RateLimit({ + requests: 5, + window: '1h', + key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous' + }) + async requestSupply(input: { item: string }, ctx: ExecutionContext) { + // ... + } +} +``` + +--- + +## Handling File Uploads in Tools +NitroStack supports file uploads from MCP clients (like NitroStudio) by passing the file as a base64-encoded string inside a tool's input parameters. + +### 1. Declaring Input Schema for File Uploads +To accept an uploaded file, define three Zod fields in your tool's `inputSchema`: +* `file_name`: The name of the file (e.g. `report.csv`). +* `file_type`: The MIME type (e.g. `text/csv`). +* `file_content`: The base64-encoded string containing the file data. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; + +export class FileTools { + @Tool({ + name: 'upload_document', + description: 'Upload a text document or image.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async uploadDocument(input: any, ctx: ExecutionContext) { + // Processing logic + } +} +``` + +### 2. Decoding Base64 Payloads +File uploads can arrive in two formats depending on the client: +1. **Data URL format**: `data:image/png;base64,iVBORw0KGgo...` +2. **Raw Base64 format**: `iVBORw0KGgo...` + +Use the following universal decoder pattern to parse either format into a Node `Buffer`: + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; + +function decodeBase64File(content: string): Buffer { + const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (matches && matches.length === 3) { + // Data URL format - decode matches[2] + return Buffer.from(matches[2], 'base64'); + } else { + // Raw base64 format - decode input directly + return Buffer.from(content, 'base64'); + } +} +``` + +### 3. Secure File Saving Example +Always validate the directory paths to prevent directory traversal attacks when saving files to disk. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); + +export class SecureUploadTools { + @Tool({ + name: 'save_uploaded_file', + description: 'Decodes and saves an uploaded file securely.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async saveFile(input: any, ctx: ExecutionContext) { + // Ensure uploads directory exists + if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + } + + // Secure destination path to prevent path traversal + const safeName = path.basename(input.file_name); + const filePath = path.join(UPLOAD_DIR, safeName); + if (!filePath.startsWith(UPLOAD_DIR)) { + throw new Error('Invalid file path detected (path traversal).'); + } + + // Decode and write to disk + const buffer = decodeBase64File(input.file_content); + fs.writeFileSync(filePath, buffer); + + ctx.logger.info(`Successfully saved file: ${safeName}`); + return { success: true, path: filePath }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/ui-widgets/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gemini/skills/ui-widgets/SKILL.md @@ -0,0 +1,251 @@ +--- +name: nitrostack-ui-widgets +description: Best practices for linking tools to interactive frontend widgets using @Widget and @nitrostack/widgets SDK (including state sync, tool calling, display modes, media queries, and chat actions). +--- + +## When to Use +Use this skill when designing, building, or modifying interactive user interface widgets that display custom React content inside AI clients or NitroStudio. + +--- + +## 1. Backend Definition (`@Widget`) +To display a React-based widget for a tool's output, decorate the tool method with `@Widget`. + +### Options: +* **String Route**: A simple string representing the route identifier in the frontend React app (e.g. `'product-card'`). +* **Object Route**: Object including: + * `route` (required): The route path. + * `domain` (optional): Allowed sandbox domain. + * `csp` (optional): Content Security Policy guidelines. + +### Example: +```typescript +import { Tool, Widget, z } from '@nitrostack/core'; + +export class CatalogTools { + @Tool({ + name: 'fetch_product', + description: 'Get product information by barcode.', + inputSchema: z.object({ barcode: z.string() }), + }) + @Widget('product-details') // Maps to the "product-details" frontend component + async fetchProduct(input: { barcode: string }) { + return { + name: 'Super Nitro Energy Drink', + price: 2.99, + sku: input.barcode, + }; + } +} +``` + +--- + +## 2. Frontend React Widget (`@nitrostack/widgets`) +In your React widget frontend application (typically a Next.js client component), use the `useWidgetSDK` hook to receive input data from the client host. + +### React Component Example: +```tsx +'use client'; + +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +interface ProductData { + name: string; + price: number; + sku: string; +} + +export default function ProductDetailsWidget() { + const { isReady, getToolOutput, theme } = useWidgetSDK(); + const data = getToolOutput(); + + if (!isReady) { + return
Connecting to host...
; + } + + if (!data) { + return
No product data received.
; + } + + return ( +
+

{data.name}

+

${data.price.toFixed(2)}

+ SKU: {data.sku} +
+ ); +} +``` + +--- + +## 3. State Management & Synchronization (`useWidgetState`) +Use `useWidgetState` to manage and persist client-side widget state (e.g. selected tabs, filter values, input states). This state automatically synchronizes with the host application context, persisting it across page re-renders. + +### Example: +```tsx +import React from 'react'; +import { useWidgetState } from '@nitrostack/widgets'; + +export default function StationPanelWidget() { + const [state, setState] = useWidgetState(() => ({ + selectedTab: 'overview', + showExtendedInfo: false, + })); + + return ( +
+ +

Current Tab: {state?.selectedTab}

+
+ ); +} +``` + +--- + +## 4. Calling Core Tools from Widgets (`callTool`) +You can invoke other backend MCP tools directly from the frontend widget using `callTool`. This is useful for tool chaining or triggering detailed audits. + +### Example: +```tsx +import React, { useState } from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function SystemDiagnostics() { + const { callTool, isReady } = useWidgetSDK(); + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState(null); + + const runDiagnostic = async () => { + if (!isReady) return; + setIsRunning(true); + try { + const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' }); + setResult(response.result as string); + } catch (err) { + setResult('Diagnostic execution failed.'); + } finally { + setIsRunning(false); + } + }; + + return ( + + ); +} +``` + +--- + +## 5. Layout & Display Controls +Widgets can dynamically request size mode changes (fullscreen, inline, picture-in-picture) and adapt layouts to safe areas (dynamic islands/notches) or maximum height constraints. + +### Key Methods: +* `requestFullscreen()`: Switch host widget display to fullscreen. +* `requestInline()`: Switch host widget display back to inline. +* `requestPip()`: Float widget in Picture-in-Picture. +* `requestClose()`: Dismiss the widget completely. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function StatusBoard() { + const { + requestFullscreen, + requestInline, + requestClose, + displayMode, // Reactive property ('fullscreen' | 'inline' | 'pip') + maxHeight, // Reactive maxHeight constraint (in pixels) + getSafeArea // Insets data: { top, right, bottom, left } + } = useWidgetSDK(); + + const safeArea = getSafeArea() || { top: 0, bottom: 0 }; + + return ( +
+

Mode: {displayMode}

+ + + +
+ ); +} +``` + +--- + +## 6. Chat Navigation & Actions +Widgets can interact with the host chat pane using external browser links and follow-up prompts. + +### Key Methods: +* `openExternal(url)`: Open the target URL safely in the user's primary external browser. +* `sendFollowUpMessage(prompt)`: Insert a message into the chat flow, automatically submitting it to the LLM agent. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function MissionControl() { + const { openExternal, sendFollowUpMessage } = useWidgetSDK(); + + return ( +
+ {/* Open external documentation */} + + + {/* Ask LLM agent directly from the widget */} + +
+ ); +} +``` + +--- + +## 7. Media & Accessibility Queries +The SDK provides helper utilities to query target client capabilities for styling or accessibility. + +### Key Utilities: +* `prefersReducedMotion()`: Returns `true` if client settings specify reduced motion. Disable animations. +* `isPrimarilyTouchDevice()`: Returns `true` if the device has a coarse pointer (e.g. touch/mobile). Increase button target sizes. +* `isHoverAvailable()`: Returns `true` if pointer supports hover states. +* `prefersDarkColorScheme()`: Returns `true` if the system theme is dark. + +### Example: +```tsx +import React from 'react'; +import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets'; + +export default function AccessiblePanel() { + const isTouch = isPrimarilyTouchDevice(); + const reducedMotion = prefersReducedMotion(); + + return ( +
+ +
+ ); +} +``` + +--- + +## 8. Testing Widgets +* Open your project in **NitroStudio** for visual preview. +* Invoke the tool from the AI chat or testing pane to verify the widget updates instantly with the returned JSON structure. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gitignore b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gitignore new file mode 100644 index 000000000..b5b5532bc --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.gitignore @@ -0,0 +1,57 @@ +# Dependencies +node_modules/ +src/widgets/node_modules/ + +# Build outputs +dist/ +src/widgets/.next/ +src/widgets/out/ + +# Environment files +.env +.env.local +.env.*.local + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage +coverage/ +.nyc_output/ + +# Uploads +uploads/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache +.npm/ + +# Optional eslint cache +.eslintcache + +# OAuth tokens/secrets (never commit these!) +*.pem +*.key +tokens.json diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/auth-security/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/auth-security/SKILL.md @@ -0,0 +1,141 @@ +--- +name: nitrostack-auth-security +description: Best practices for implementing JWT, API Keys, OAuth 2.1, and RBAC in a NitroStack application. +--- + +## When to Use +Use this skill when configuring security modules, implementing user authentication, restricting tool access via guards, or handling sensitive tokens. + +--- + +## 1. JSON Web Tokens (JWT) +To secure tools with JWT authentication: + +### Register `JWTModule`: +```typescript +import { JWTModule, Module, McpApp } from '@nitrostack/core'; + +@McpApp({ + server: { name: 'my-server', version: '1.0.0' } +}) +@Module({ + imports: [ + JWTModule.forRoot({ + secret: process.env.JWT_SECRET!, + expiresIn: '7d', + }), + ] +}) +export class AppModule {} +``` + +### Write a `JWTGuard`: +```typescript +import { Guard, ExecutionContext, Injectable, ConfigService } from '@nitrostack/core'; +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JWTGuard implements Guard { + constructor(private config: ConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const token = this.extractToken(context); + if (!token) return false; + + try { + const secret = this.config.get('JWT_SECRET'); + const payload = jwt.verify(token, secret) as any; + context.auth = { + subject: payload.sub, + role: payload.role, + token, + }; + return true; + } catch { + return false; + } + } + + private extractToken(context: ExecutionContext): string | null { + const auth = context.metadata?.authorization; + if (auth?.startsWith('Bearer ')) { + return auth.substring(7); + } + return null; + } +} +``` + +--- + +## 2. API Key Authentication +Use `ApiKeyModule` for service-to-service validation. + +### Register `ApiKeyModule`: +```typescript +import { ApiKeyModule, Module } from '@nitrostack/core'; + +@Module({ + imports: [ + ApiKeyModule.forRoot({ + keysEnvPrefix: 'API_KEY', // Reads API_KEY_1, API_KEY_2, etc. + headerName: 'x-api-key', + hashed: false, + }), + ] +}) +export class AppModule {} +``` + +### API Key Guard: +```typescript +import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core'; + +export class ApiKeyGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const apiKey = context.metadata?.['x-api-key'] || context.metadata?.apiKey; + if (!apiKey) return false; + + const isValid = await ApiKeyModule.validate(apiKey as string); + if (isValid) { + context.auth = { + subject: `apikey_${(apiKey as string).substring(0, 10)}`, + scopes: ['*'], + }; + return true; + } + return false; + } +} +``` + +--- + +## 3. Role-Based Access Control (RBAC) +Chain guards sequentially to implement user-role authorization. + +```typescript +import { Injectable, Guard, ExecutionContext, UseGuards, Tool, z } from '@nitrostack/core'; +import { JWTGuard } from './jwt.guard.js'; + +@Injectable() +export class AdminGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + // Requires JWTGuard to have populated context.auth first + return context.auth?.role === 'admin'; + } +} + +// Applying chained guards to a tool +export class SystemTools { + @Tool({ + name: 'reset_database', + description: 'Dangerous action: wipes database. Admin only.', + inputSchema: z.object({}), + }) + @UseGuards(JWTGuard, AdminGuard) // Chain auth first, then role check + async resetDatabase() { + return { success: true }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/mcp-app-architecture/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/mcp-app-architecture/SKILL.md @@ -0,0 +1,176 @@ +--- +name: nitrostack-mcp-app-architecture +description: Best practices and guidelines for bootstrapping, defining modules, using dependency injection, managing server lifecycles, and handling events in the NitroStack SDK. +--- + +## When to Use +Use this skill whenever you are bootstrapping a new NitroStack MCP server, creating modules, injecting services, or handling application lifecycle events. + +## Bootstrapping a NitroStack App +A NitroStack application is initialized with the `@McpApp` decorator on a root class, accompanied by a root `@Module`. + +```typescript +import { McpApp, Module } from '@nitrostack/core'; +import { DatabaseModule } from './database/database.module.js'; +import { UsersModule } from './users/users.module.js'; + +@McpApp({ + module: AppModule, + server: { + name: 'user-management-server', + version: '1.0.0', + }, +}) +@Module({ + imports: [DatabaseModule, UsersModule], +}) +export class AppModule {} +``` + +## Modules +Modules organize your application structure. Use the `@Module` decorator to define imports, exports, and providers. + +* **`imports`**: Other modules whose exported providers should be available in this module. +* **`providers`**: Services, tools, resources, or prompts that should be instantiated and managed by the DI container within this module. +* **`exports`**: Providers defined in this module that should be visible to other modules importing this one. + +```typescript +import { Module } from '@nitrostack/core'; +import { UsersService } from './users.service.js'; +import { UsersTools } from './users.tools.js'; + +@Module({ + providers: [UsersService, UsersTools], + exports: [UsersService], +}) + +## Controllers +Use the `@ControllerDecorator` (or alias it as `@Controller`) to group tools, resources, and prompts together. Controllers are automatically registered as singletons in the DI container. + +### Key Controller Options: +* **`prefix`**: A string prefix applied to every `@Tool` defined in this controller. For example, `@ControllerDecorator('github')` prefixing a tool named `create_issue` exposes it to MCP clients as `github_create_issue`. + +```typescript +import { ControllerDecorator as Controller, Tool, ExecutionContext } from '@nitrostack/core'; + +@Controller('github') +export class GitHubController { + @Tool({ + name: 'create_issue', + description: 'Create an issue in a repository', + inputSchema: z.object({ /* ... */ }) + }) + async createIssue(input: any, ctx: ExecutionContext) { + // Exposed to clients as "github_create_issue" + } +} +``` + +## Dependency Injection (DI) +NitroStack uses a robust dependency injection container to manage class instances and lifecycles. + +### Injection Lifecycles +1. **Singleton (Default)**: A single instance is shared across the entire application. +2. **Transient**: A new instance is created every time it is resolved/injected. +3. **Scoped**: A new instance is created per incoming request or context. + +```typescript +import { Injectable, Scope } from '@nitrostack/core'; + +@Injectable({ scope: Scope.SINGLETON }) +export class UsersService { + constructor(private readonly db: DatabaseService) {} + + async getUser(id: string) { + return this.db.query('SELECT * FROM users WHERE id = $1', [id]); + } +} +``` + +## Lifecycles and Hooks +Implement NestJS-style lifecycle interfaces on modules, controllers, or providers to hook into application state changes: + +* **`OnModuleInit`** (`onModuleInit`): Called after modules have initialized but before the server starts listening. +* **`OnApplicationBootstrap`** (`onApplicationBootstrap`): Called once the server is fully started and listening. +* **`OnModuleDestroy`** (`onModuleDestroy`): Called when the module or application is shutting down. +* **`BeforeApplicationShutdown`** (`beforeApplicationShutdown(signal?: string)`): Called before the application starts shutting down. Receives the OS signal (e.g. `SIGINT`). +* **`OnApplicationShutdown`** (`onApplicationShutdown(signal?: string)`): Called during shutdown. Receives the OS signal. + +```typescript +import { + Injectable, + OnModuleInit, + OnApplicationBootstrap, + OnModuleDestroy, + BeforeApplicationShutdown, + OnApplicationShutdown +} from '@nitrostack/core'; + +@Injectable() +export class DatabaseService + implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown +{ + async onModuleInit() { + await this.connect(); + } + + async onApplicationBootstrap() { + console.log('App ready to handle connections.'); + } + + async onModuleDestroy() { + await this.cleanupPendingQueries(); + } + + async beforeApplicationShutdown(signal?: string) { + console.log(`Shutting down soon (signal: ${signal}).`); + } + + async onApplicationShutdown(signal?: string) { + await this.disconnect(); + } +} +``` + +--- + +## Eventing System (`emitEvent` and `@OnEvent`) +NitroStack includes an internal eventing system to decouple components. A service or tool can emit an event using `emitEvent`, and any injectable class (like a handler service or controller) can subscribe using the `@OnEvent` decorator. + +### 1. Emitting Events +Call `emitEvent` to dispatch an event payload asynchronously. + +```typescript +import { Injectable, emitEvent } from '@nitrostack/core'; + +@Injectable() +export class SpaceShipService { + async launchShip(shipId: string) { + // Process launch... + + // Dispatch event + emitEvent('ship.launched', { + shipId, + timestamp: new Date().toISOString(), + }); + } +} +``` + +### 2. Listening to Events +Decorate a method inside any `@Injectable()` class with `@OnEvent('event_pattern')` to register it as an event handler. + +```typescript +import { Injectable, OnEvent } from '@nitrostack/core'; + +@Injectable({ deps: [] }) +export class FlightLogHandler { + @OnEvent('ship.launched') + async logLaunch(data: { shipId: string; timestamp: string }) { + console.error(`🚀 [EVENT] Ship ${data.shipId} was successfully launched at ${data.timestamp}`); + } +} +``` + +> [!NOTE] +> For the `@OnEvent` decorator to register properly, the containing class must be declared as a provider inside an active module. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/middleware-pipeline/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/middleware-pipeline/SKILL.md @@ -0,0 +1,235 @@ +--- +name: nitrostack-middleware-pipeline +description: Best practices for implementing and applying Guards, Interceptors, Middleware, Pipes, and Exception Filters in the NitroStack SDK. +--- + +## When to Use +Use this skill when implementing request validation, authorization checks, response mapping, logging, error handling, or performance tracking on NitroStack tool methods. + +--- + +## 1. Guards (`Guard` and `@UseGuards`) +Guards determine if a request should be processed by a tool handler based on authentication, authorization, or other conditions. + +### Interface: +```typescript +import { Guard, ExecutionContext } from '@nitrostack/core'; + +export interface Guard { + canActivate(context: ExecutionContext): boolean | Promise; +} +``` + +### Example: +```typescript +import { Guard, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class RolesGuard implements Guard { + async canActivate(context: ExecutionContext): Promise { + const userRoles = context.clientMetadata?.roles || []; + return userRoles.includes('admin'); + } +} +``` + +Apply the guard using `@UseGuards(...)`: +```typescript +import { Tool, UseGuards, z } from '@nitrostack/core'; +import { RolesGuard } from './roles.guard.js'; + +export class AdminTools { + @Tool({ + name: 'delete_system_logs', + description: 'Delete all system logs from the server.', + inputSchema: z.object({}), + }) + @UseGuards(RolesGuard) + async deleteLogs() { + return { success: true }; + } +} +``` + +--- + +## 2. Interceptors (`InterceptorInterface` and `@UseInterceptors`) +Interceptors can transform/intercept input arguments or mapped output from a tool method execution. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface InterceptorInterface { + intercept(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { InterceptorInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class TimingInterceptor implements InterceptorInterface { + async intercept(context: ExecutionContext, next: () => Promise): Promise { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + context.logger.info(`Execution took ${duration}ms`); + return { + ...result, + _meta: { durationMs: duration } + }; + } +} +``` + +--- + +## 3. Exception Filters (`ExceptionFilterInterface` and `@UseFilters`) +Exception filters catch any errors thrown within guards, interceptors, or the tool handlers themselves, mapping them into user-friendly JSON payloads. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext): unknown | Promise; +} +``` + +### Example: +```typescript +import { ExceptionFilterInterface, ExecutionContext, Injectable } from '@nitrostack/core'; + +@Injectable() +export class CustomExceptionFilter implements ExceptionFilterInterface { + catch(exception: unknown, context: ExecutionContext) { + const message = exception instanceof Error ? exception.message : 'Unknown error'; + return { + error: true, + message, + timestamp: new Date().toISOString() + }; + } +} +``` + +Apply the filter using `@UseFilters(...)` on a tool method: + +```typescript +import { Tool, UseFilters, z } from '@nitrostack/core'; +import { CustomExceptionFilter } from './custom-exception.filter.js'; + +export class LoggingTools { + @Tool({ + name: 'generate_report', + description: 'Generates system usage reports.', + inputSchema: z.object({}), + }) + @UseFilters(CustomExceptionFilter) + async generateReport() { + throw new Error('Report generation is not implemented yet.'); + } +} +``` + +--- + +## 4. Middleware (`MiddlewareInterface`, `@Middleware` and `@UseMiddleware`) +Middleware executes before the request reaches the tool handler, and can wrap the handler execution by invoking `next()`. + +### Interface: +```typescript +import { ExecutionContext } from '@nitrostack/core'; + +export interface MiddlewareInterface { + use(context: ExecutionContext, next: () => Promise): Promise; +} +``` + +### Example: +```typescript +import { Middleware, MiddlewareInterface, ExecutionContext } from '@nitrostack/core'; + +@Middleware() +export class LoggingMiddleware implements MiddlewareInterface { + async use(context: ExecutionContext, next: () => Promise): Promise { + context.logger.info(`Entering tool: ${context.toolName}`); + try { + const result = await next(); + context.logger.info(`Exiting tool: ${context.toolName}`); + return result; + } catch (error) { + context.logger.error(`Error in tool: ${error}`); + throw error; + } + } +} +``` + +Apply the middleware using `@UseMiddleware(...)` on a tool method: +```typescript +import { Tool, UseMiddleware, z } from '@nitrostack/core'; +import { LoggingMiddleware } from './logging.middleware.js'; + +export class StationTools { + @Tool({ + name: 'fetch_logs', + description: 'Fetch station operations logs.', + inputSchema: z.object({}), + }) + @UseMiddleware(LoggingMiddleware) + async fetchLogs() { + return { status: 'operational' }; + } +} +``` + +--- + +## 5. Pipes (`PipeInterface`, `@Pipe` and `@UsePipes`) +Pipes are used to transform or validate input arguments before they reach the tool handler method. + +### Interface: +```typescript +import { ArgumentMetadata } from '@nitrostack/core'; + +export interface PipeInterface { + transform(value: T, metadata: ArgumentMetadata): R | Promise; +} +``` + +### Example: +```typescript +import { Pipe, PipeInterface, ArgumentMetadata } from '@nitrostack/core'; + +@Pipe() +export class TrimPipe implements PipeInterface, Record> { + transform(value: Record, metadata: ArgumentMetadata) { + const trimmed: Record = {}; + for (const [key, val] of Object.entries(value)) { + trimmed[key] = typeof val === 'string' ? val.trim() : val; + } + return trimmed; + } +} +``` + +Apply the pipe using `@UsePipes(...)` on a tool method: +```typescript +import { Tool, UsePipes, z } from '@nitrostack/core'; +import { TrimPipe } from './trim.pipe.js'; + +export class MessagingTools { + @Tool({ + name: 'send_message', + description: 'Send a message to other stations.', + inputSchema: z.object({ text: z.string() }), + }) + @UsePipes(TrimPipe) + async sendMessage(input: { text: string }) { + return { sentText: input.text }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/tools-resources-prompts/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/tools-resources-prompts/SKILL.md @@ -0,0 +1,287 @@ +--- +name: nitrostack-tools-resources-prompts +description: Guidelines and patterns for defining Tools, Resources, and Prompts in a NitroStack application with schema validation via Zod, including caching, rate-limiting, and base64 file uploads. +--- + +## When to Use +Use this skill whenever you are defining, editing, or validating tools, resources, or prompts on a NitroStack MCP server. + +## Defining Tools with `@Tool` +An MCP tool exposes a function that an AI client can invoke. Decorate a service or controller method with `@Tool`. + +### Key Tool Options: +* `name`: Kebab-case or snake_case unique identifier. +* `description`: Detailed description explaining when and how the client should use it. +* `inputSchema`: A Zod object schema for strict validation of inputs. +* `outputSchema` (optional): Zod schema validating the output structure. + +```typescript +import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core'; + +@Controller('weather') +export class WeatherService { + @Tool({ + name: 'get_current_weather', + description: 'Get the current weather forecast for a specific city.', + inputSchema: z.object({ + city: z.string().describe('The name of the city, e.g., San Francisco'), + unit: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }) + @InitialTool() // Auto-invoked when the AI client initializes/starts + async getWeather( + input: { city: string; unit: 'celsius' | 'fahrenheit' }, + ctx: ExecutionContext + ) { + ctx.logger.info(`Fetching weather for ${input.city}`); + // implementation + return { + city: input.city, + temp: 22, + condition: 'Sunny', + }; + } +} +``` + +## Defining Resources with `@Resource` +An MCP resource exposes static or dynamic data files/URIs that the AI client can read. + +### Key Resource Options: +* `uri`: URI pattern (e.g., `git://{owner}/{repo}/file` or static `app://config`). +* `name`: Unique name of the resource. +* `description`: Explanation of what data this resource provides. +* `mimeType`: Mime type of the response (e.g., `text/plain`, `application/json`). + +```typescript +import { Resource, ExecutionContext } from '@nitrostack/core'; + +export class ConfigResources { + @Resource({ + uri: 'app://settings', + name: 'Application Settings', + description: 'System-wide configuration settings and parameters.', + mimeType: 'application/json', + }) + async getSettings(ctx: ExecutionContext) { + return { + environment: 'development', + debugMode: true, + }; + } +} +``` + +## Defining Prompts with `@Prompt` +An MCP prompt exposes reusable templates or instruction sets that guide LLMs. + +### Key Prompt Options: +* `name`: Name of the prompt. +* `description`: Describes what task this prompt helps accomplish. +* `arguments`: Declares parameters the client can supply to customize the prompt template. + +```typescript +import { Prompt, ExecutionContext } from '@nitrostack/core'; + +export class PromptTemplates { + @Prompt({ + name: 'code_review', + description: 'Provide an intensive code review for a given code snippet.', + arguments: [ + { name: 'language', description: 'The programming language, e.g., TypeScript', required: true }, + { name: 'code', description: 'The code snippet to review', required: true }, + ], + }) + async getCodeReviewPrompt( + args: { language: string; code: string }, + ctx: ExecutionContext + ) { + return { + messages: [ + { + role: 'user', + content: `You are an expert software engineer. Review this ${args.language} code:\n\n${args.code}`, + }, + ], + }; + } +} +``` + +--- + +## Tool Policies: Caching (`@Cache`) and Rate Limiting (`@RateLimit`) +You can control tool execution behaviors (such as performance optimization and throttling) using method decorators. + +### 1. Caching with `@Cache` +Use `@Cache` to cache tool execution outputs for a specified duration (TTL in seconds). This reduces database or API overhead for frequent identical requests. + +#### Options: +* `ttl`: Cache time-to-live in seconds (required). +* `key` (optional): Custom function `(input: any, context?: any) => string` that returns a unique cache key based on inputs. If not defined, a key is auto-generated from serialized input arguments. + +#### Example: +```typescript +import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core'; + +export class StationTools { + @Tool({ + name: 'get_system_status', + description: 'Fetch real-time station metrics. Response is cached.', + inputSchema: z.object({}), + }) + @Cache({ ttl: 60 }) // Caches status for 60 seconds + async getSystemStatus() { + return { temperature: 21.5, oxygen: 0.98 }; + } + + @Tool({ + name: 'get_crew_status', + description: 'Fetch status of a crew member. Cached by crew ID.', + inputSchema: z.object({ id: z.string() }), + }) + @Cache({ + ttl: 300, + key: (input) => `crew:status:${input.id}` + }) + async getCrewStatus(input: { id: string }) { + // ... + } +} +``` + +### 2. Rate Limiting with `@RateLimit` +Use `@RateLimit` to restrict the number of tool invocations within a specified time window to prevent client abuse. + +#### Options: +* `requests`: Number of allowed requests in the window (required). +* `window`: Throttling duration window (required). Supports formats like `'1s'`, `'1m'`, `'1h'`. +* `key` (optional): Custom function `(context: ExecutionContext) => string` to group rate limits. Useful for rate-limiting per user role or API key. + +#### Example: +```typescript +import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core'; + +export class DiagnosticTools { + @Tool({ + name: 'run_deep_diagnostic', + description: 'Run intensive diagnostics. Rate limited.', + inputSchema: z.object({}), + }) + @RateLimit({ requests: 3, window: '1m' }) // Max 3 requests per minute globally + async runDeepDiagnostic() { + return { diagnosticReport: 'All systems operational.' }; + } + + @Tool({ + name: 'request_supply_drop', + description: 'Request inventory supplies. Rate limited per user.', + inputSchema: z.object({ item: z.string() }), + }) + @RateLimit({ + requests: 5, + window: '1h', + key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous' + }) + async requestSupply(input: { item: string }, ctx: ExecutionContext) { + // ... + } +} +``` + +--- + +## Handling File Uploads in Tools +NitroStack supports file uploads from MCP clients (like NitroStudio) by passing the file as a base64-encoded string inside a tool's input parameters. + +### 1. Declaring Input Schema for File Uploads +To accept an uploaded file, define three Zod fields in your tool's `inputSchema`: +* `file_name`: The name of the file (e.g. `report.csv`). +* `file_type`: The MIME type (e.g. `text/csv`). +* `file_content`: The base64-encoded string containing the file data. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; + +export class FileTools { + @Tool({ + name: 'upload_document', + description: 'Upload a text document or image.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async uploadDocument(input: any, ctx: ExecutionContext) { + // Processing logic + } +} +``` + +### 2. Decoding Base64 Payloads +File uploads can arrive in two formats depending on the client: +1. **Data URL format**: `data:image/png;base64,iVBORw0KGgo...` +2. **Raw Base64 format**: `iVBORw0KGgo...` + +Use the following universal decoder pattern to parse either format into a Node `Buffer`: + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; + +function decodeBase64File(content: string): Buffer { + const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (matches && matches.length === 3) { + // Data URL format - decode matches[2] + return Buffer.from(matches[2], 'base64'); + } else { + // Raw base64 format - decode input directly + return Buffer.from(content, 'base64'); + } +} +``` + +### 3. Secure File Saving Example +Always validate the directory paths to prevent directory traversal attacks when saving files to disk. + +```typescript +import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); + +export class SecureUploadTools { + @Tool({ + name: 'save_uploaded_file', + description: 'Decodes and saves an uploaded file securely.', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content'), + }) + }) + async saveFile(input: any, ctx: ExecutionContext) { + // Ensure uploads directory exists + if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + } + + // Secure destination path to prevent path traversal + const safeName = path.basename(input.file_name); + const filePath = path.join(UPLOAD_DIR, safeName); + if (!filePath.startsWith(UPLOAD_DIR)) { + throw new Error('Invalid file path detected (path traversal).'); + } + + // Decode and write to disk + const buffer = decodeBase64File(input.file_content); + fs.writeFileSync(filePath, buffer); + + ctx.logger.info(`Successfully saved file: ${safeName}`); + return { success: true, path: filePath }; + } +} +``` diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/ui-widgets/SKILL.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/.opencode/skills/ui-widgets/SKILL.md @@ -0,0 +1,251 @@ +--- +name: nitrostack-ui-widgets +description: Best practices for linking tools to interactive frontend widgets using @Widget and @nitrostack/widgets SDK (including state sync, tool calling, display modes, media queries, and chat actions). +--- + +## When to Use +Use this skill when designing, building, or modifying interactive user interface widgets that display custom React content inside AI clients or NitroStudio. + +--- + +## 1. Backend Definition (`@Widget`) +To display a React-based widget for a tool's output, decorate the tool method with `@Widget`. + +### Options: +* **String Route**: A simple string representing the route identifier in the frontend React app (e.g. `'product-card'`). +* **Object Route**: Object including: + * `route` (required): The route path. + * `domain` (optional): Allowed sandbox domain. + * `csp` (optional): Content Security Policy guidelines. + +### Example: +```typescript +import { Tool, Widget, z } from '@nitrostack/core'; + +export class CatalogTools { + @Tool({ + name: 'fetch_product', + description: 'Get product information by barcode.', + inputSchema: z.object({ barcode: z.string() }), + }) + @Widget('product-details') // Maps to the "product-details" frontend component + async fetchProduct(input: { barcode: string }) { + return { + name: 'Super Nitro Energy Drink', + price: 2.99, + sku: input.barcode, + }; + } +} +``` + +--- + +## 2. Frontend React Widget (`@nitrostack/widgets`) +In your React widget frontend application (typically a Next.js client component), use the `useWidgetSDK` hook to receive input data from the client host. + +### React Component Example: +```tsx +'use client'; + +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +interface ProductData { + name: string; + price: number; + sku: string; +} + +export default function ProductDetailsWidget() { + const { isReady, getToolOutput, theme } = useWidgetSDK(); + const data = getToolOutput(); + + if (!isReady) { + return
Connecting to host...
; + } + + if (!data) { + return
No product data received.
; + } + + return ( +
+

{data.name}

+

${data.price.toFixed(2)}

+ SKU: {data.sku} +
+ ); +} +``` + +--- + +## 3. State Management & Synchronization (`useWidgetState`) +Use `useWidgetState` to manage and persist client-side widget state (e.g. selected tabs, filter values, input states). This state automatically synchronizes with the host application context, persisting it across page re-renders. + +### Example: +```tsx +import React from 'react'; +import { useWidgetState } from '@nitrostack/widgets'; + +export default function StationPanelWidget() { + const [state, setState] = useWidgetState(() => ({ + selectedTab: 'overview', + showExtendedInfo: false, + })); + + return ( +
+ +

Current Tab: {state?.selectedTab}

+
+ ); +} +``` + +--- + +## 4. Calling Core Tools from Widgets (`callTool`) +You can invoke other backend MCP tools directly from the frontend widget using `callTool`. This is useful for tool chaining or triggering detailed audits. + +### Example: +```tsx +import React, { useState } from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function SystemDiagnostics() { + const { callTool, isReady } = useWidgetSDK(); + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState(null); + + const runDiagnostic = async () => { + if (!isReady) return; + setIsRunning(true); + try { + const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' }); + setResult(response.result as string); + } catch (err) { + setResult('Diagnostic execution failed.'); + } finally { + setIsRunning(false); + } + }; + + return ( + + ); +} +``` + +--- + +## 5. Layout & Display Controls +Widgets can dynamically request size mode changes (fullscreen, inline, picture-in-picture) and adapt layouts to safe areas (dynamic islands/notches) or maximum height constraints. + +### Key Methods: +* `requestFullscreen()`: Switch host widget display to fullscreen. +* `requestInline()`: Switch host widget display back to inline. +* `requestPip()`: Float widget in Picture-in-Picture. +* `requestClose()`: Dismiss the widget completely. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function StatusBoard() { + const { + requestFullscreen, + requestInline, + requestClose, + displayMode, // Reactive property ('fullscreen' | 'inline' | 'pip') + maxHeight, // Reactive maxHeight constraint (in pixels) + getSafeArea // Insets data: { top, right, bottom, left } + } = useWidgetSDK(); + + const safeArea = getSafeArea() || { top: 0, bottom: 0 }; + + return ( +
+

Mode: {displayMode}

+ + + +
+ ); +} +``` + +--- + +## 6. Chat Navigation & Actions +Widgets can interact with the host chat pane using external browser links and follow-up prompts. + +### Key Methods: +* `openExternal(url)`: Open the target URL safely in the user's primary external browser. +* `sendFollowUpMessage(prompt)`: Insert a message into the chat flow, automatically submitting it to the LLM agent. + +### Example: +```tsx +import React from 'react'; +import { useWidgetSDK } from '@nitrostack/widgets'; + +export default function MissionControl() { + const { openExternal, sendFollowUpMessage } = useWidgetSDK(); + + return ( +
+ {/* Open external documentation */} + + + {/* Ask LLM agent directly from the widget */} + +
+ ); +} +``` + +--- + +## 7. Media & Accessibility Queries +The SDK provides helper utilities to query target client capabilities for styling or accessibility. + +### Key Utilities: +* `prefersReducedMotion()`: Returns `true` if client settings specify reduced motion. Disable animations. +* `isPrimarilyTouchDevice()`: Returns `true` if the device has a coarse pointer (e.g. touch/mobile). Increase button target sizes. +* `isHoverAvailable()`: Returns `true` if pointer supports hover states. +* `prefersDarkColorScheme()`: Returns `true` if the system theme is dark. + +### Example: +```tsx +import React from 'react'; +import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets'; + +export default function AccessiblePanel() { + const isTouch = isPrimarilyTouchDevice(); + const reducedMotion = prefersReducedMotion(); + + return ( +
+ +
+ ); +} +``` + +--- + +## 8. Testing Widgets +* Open your project in **NitroStudio** for visual preview. +* Invoke the tool from the AI chat or testing pane to verify the widget updates instantly with the returned JSON structure. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/README.md b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/README.md new file mode 100644 index 000000000..9dba89505 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/README.md @@ -0,0 +1,49 @@ +# NitroStack Starter Template + +Minimal template for learning NitroStack fundamentals with a calculator-focused +MCP server and basic widgets. + +## What This Template Includes + +- `calculator` module with tools, resources, and prompts +- TypeScript + Zod validation setup +- Widget-ready project structure +- Production-friendly npm scripts + +## Quick Start + +```bash +npx @nitrostack/cli init my-server --template typescript-starter +cd my-server +npm run dev +``` + +## Common Commands + +```bash +npm run dev +npm run build +npm start +``` + +## NitroStudio + +NitroStudio is the recommended way to test and debug this template during +development. + +- Download: +- Studio: + +## Links + +- Docs: +- Templates docs: +- Main repository: + +## Community + +- Discord: +- X: +- YouTube: +- LinkedIn: +- GitHub: diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/_smoke_test.mjs b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/_smoke_test.mjs new file mode 100644 index 000000000..f65dd9b02 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/_smoke_test.mjs @@ -0,0 +1,136 @@ +import { spawn } from "node:child_process"; +import http from "node:http"; +import WebSocket from "ws"; + +const API_PORT = 5000; +const WS_PORT = 8080; +const BASE = `http://localhost:${API_PORT}`; + +function get(path) { + return new Promise((resolve, reject) => { + http + .get(`${BASE}${path}`, (res) => { + let body = ""; + res.on("data", (c) => (body += c)); + res.on("end", () => resolve(JSON.parse(body))); + }) + .on("error", reject); + }); +} + +function post(path, body) { + return new Promise((resolve, reject) => { + const data = JSON.stringify(body); + const req = http.request( + `${BASE}${path}`, + { method: "POST", headers: { "Content-Type": "application/json" } }, + (res) => { + let out = ""; + res.on("data", (c) => (out += c)); + res.on("end", () => resolve(JSON.parse(out))); + } + ); + req.on("error", reject); + req.write(data); + req.end(); + }); +} + +async function waitForServer(timeoutMs = 15000) { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + try { + await get("/api/health"); + return; + } catch { + await new Promise((r) => setTimeout(r, 300)); + } + } + throw new Error("Server did not become ready in time"); +} + +async function main() { + const server = spawn("node", ["dist/api/server.js"], { + stdio: "inherit", + cwd: process.cwd(), + }); + + let failed = false; + try { + await waitForServer(); + console.log("[smoke] server ready"); + + await post("/api/device/register", { + id: "ESP32_S3_001", + name: "Guardian Bridge", + }); + await post("/api/monitoring/start", { deviceId: "ESP32_S3_001" }); + + const ws = new WebSocket(`ws://localhost:${WS_PORT}`); + const liveUpdate = new Promise((resolve) => { + ws.on("message", (data) => { + const msg = JSON.parse(data.toString()); + if (msg.event === "LIVE_UPDATE") resolve(msg.data); + }); + }); + await new Promise((r) => ws.on("open", r)); + + const packet = { + deviceId: "ESP32_S3_001", + timestamp: new Date().toISOString(), + rawPacket: { + id: 1, + mac: "1a:00:00:00:00:00", + rssi: -36, + channel: 11, + noise_floor: -90, + len: 5, + local_timestamp: 12345, + csi: [10, -20, 30, 5, -8, 12, -4, 3, -1, 9], + schema: "s3", + }, + }; + const bridgeResult = await post("/api/bridge", packet); + console.log("[smoke] /api/bridge ->", JSON.stringify(bridgeResult)); + + const liveData = await liveUpdate; + console.log("[smoke] LIVE_UPDATE ->", JSON.stringify(liveData)); + + const live = await get("/api/live"); + const monitor = await get("/api/monitor"); + const alert = await get("/api/alert"); + const health = await get("/api/health"); + const csi = await get("/api/csi/latest"); + + console.log("[smoke] /api/live ->", JSON.stringify(live)); + console.log("[smoke] /api/monitor ->", JSON.stringify(monitor)); + console.log("[smoke] /api/alert ->", JSON.stringify(alert)); + console.log("[smoke] /api/health ->", JSON.stringify(health)); + console.log("[smoke] /api/csi/latest ->", JSON.stringify(csi)); + + const checks = [ + ["bridge processed", bridgeResult?.processed === true], + ["live respiration set", typeof live.respiration === "number"], + ["monitor packet rate > 0", monitor.packetRate > 0], + ["health websocketClients >= 1", health.websocketClients >= 1], + ["csi amplitudes length", Array.isArray(csi.amplitudes) && csi.amplitudes.length === 10], + ["alert has time field", typeof alert.time === "string"], + ]; + + failed = checks.some(([, ok]) => !ok); + for (const [name, ok] of checks) { + console.log(`[smoke] ${ok ? "PASS" : "FAIL"} ${name}`); + } + + ws.close(); + } catch (err) { + failed = true; + console.error("[smoke] ERROR:", err); + } finally { + server.kill(); + } + + process.exit(failed ? 1 : 0); +} + +main(); diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/package-lock.json b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/package-lock.json new file mode 100644 index 000000000..1a1a20f1f --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/package-lock.json @@ -0,0 +1,4691 @@ +{ + "name": "GuardianSenseBackend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "GuardianSenseBackend", + "version": "1.0.0", + "dependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "@nitrostack/core": "^1", + "cors": "^2.8.6", + "dotenv": "^16.3.1", + "express": "^5.2.1", + "ws": "^8.21.1", + "zod": "^3.22.4" + }, + "devDependencies": { + "@nitrostack/cli": "^1", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/node": "^22.10.0", + "@types/ws": "^8.18.1", + "typescript": "^5.3.3" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", + "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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/@isaacs/cliui/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/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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/@modelcontextprotocol/ext-apps": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.5.tgz", + "integrity": "sha512-TjPH2S2y5UEGKhmI6+XGFuqfqOV4ppe1x6DA3txnUaEWkgtA4G5vo14jGKFZmegdkZ1H4QMLyujLvoU1BEdnAg==", + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "dependencies": { + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@nitrostack/cli": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@nitrostack/cli/-/cli-1.0.15.tgz", + "integrity": "sha512-xyIbeAj2/Tpd2khh6Xq1l8y1rbrxQ0t1/c3836g9WrWqC8aNFIKoUvTLuPEZoF4x4ATyjPoZ2NIK90pywuuCRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "archiver": "^7.0.1", + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "commander": "^12.1.0", + "esbuild": "^0.24.0", + "fs-extra": "^11.3.2", + "inquirer": "^9.3.7", + "open": "^10.1.0", + "ora": "^8.1.1", + "posthog-node": "^5.21.2" + }, + "bin": { + "cli": "dist/index.js", + "nitrostack-cli": "dist/index.js", + "nitrostack-pack": "dist/pack/standalone.js" + } + }, + "node_modules/@nitrostack/core": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@nitrostack/core/-/core-1.0.14.tgz", + "integrity": "sha512-FfG5rOxZwAztHiwPqRPj3xjgoiiPa1A06y2BBqGRlNAkK8R5izImD/f3zhvo1OrGKtoDC/qEw2iykKK24UR/FA==", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.4", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "express": "^4.21.2", + "jose": "^6.1.0", + "jsonwebtoken": "^9.0.2", + "reflect-metadata": "^0.2.1", + "uuid": "^11.0.5", + "winston": "^3.17.0", + "ws": "^8.18.3", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0" + } + }, + "node_modules/@nitrostack/core/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@nitrostack/core/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/@nitrostack/core/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nitrostack/core/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@nitrostack/core/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nitrostack/core/node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nitrostack/core/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nitrostack/core/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nitrostack/core/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nitrostack/core/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/@nitrostack/core/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nitrostack/core/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nitrostack/core/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nitrostack/core/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/@posthog/core": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.46.1.tgz", + "integrity": "sha512-EoCFduRkvrg9E5ylMi4QnZCjlAdRJCq6tJouWfngBVR79XSI4iPvIWYA+CdzokAjk+TfSVBFVJ++4Im3r+T0Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@posthog/types": "^1.399.0" + } + }, + "node_modules/@posthog/types": { + "version": "1.399.0", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.399.0.tgz", + "integrity": "sha512-/WDwBzqIPko8VJ1B+0rlso2XQEz9+2sqtsY9Tqy3p1GhgTqsFakcz/PmMpAnA321LTEZVRcO6x5hAwABV4yrDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "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/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/ansi-styles/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/ansi-styles/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/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/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "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/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", + "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.6.tgz", + "integrity": "sha512-iQxPClE07hETVpbRoX7JXX3v/ZQViCxe/SYCxylRLzdEx1xJAufPptfiOqR8tqiCtmbtMDANKWszzjLu1PMAZQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "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/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "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/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "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==", + "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/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "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/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "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/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "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/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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==", + "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/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "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/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz", + "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "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/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "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/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.33", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.33.tgz", + "integrity": "sha512-+SwvkaiJtxsiPjhy9LivY/1m7UsNqCJetM1BrZl9A5DkQhlbHQDU730mMiDPWjnoCYOM8Chf3WrCJw27kNTPFQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "9.3.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.8.tgz", + "integrity": "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.2", + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/inquirer/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/inquirer/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/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/inquirer/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/inquirer/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-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-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-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "license": "ISC" + }, + "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/jose": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.5.tgz", + "integrity": "sha512-2E5L2yRp03FnwreJLJX8/r7mHiZICCf8kG7fAsTWkSQTDAcc46NIZoQLKy+EJ8sPoJlxyS4OQR5H70LjIZZlIQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/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/lazystream/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/lazystream/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/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "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==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "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/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/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=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/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "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/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "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/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/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/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/posthog-node": { + "version": "5.47.3", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.47.3.tgz", + "integrity": "sha512-mhKaZOGLgD5aKKTj6xNRE2K9vRJnRIj4FNZeguDNnCR0k8RKJh71KO78+UqVdOONBsMzoqb01AD/B+TtsK7YSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@posthog/core": "^1.46.1" + }, + "engines": { + "node": "^20.20.0 || >=22.22.0" + }, + "peerDependencies": { + "rxjs": "^7.0.0" + }, + "peerDependenciesMeta": { + "rxjs": { + "optional": true + } + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.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/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "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==", + "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/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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/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/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": { + "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/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "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/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/package.json b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/package.json new file mode 100644 index 000000000..2674aa1bd --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/package.json @@ -0,0 +1,37 @@ +{ + "name": "GuardianSenseBackend", + "version": "1.0.0", + "private": true, + "type": "module", + "description": "GuardianSense AI Backend using NitroStack", + "scripts": { + "dev": "nitrostack-cli dev", + "build": "nitrostack-cli build", + "start": "npm run build && nitrostack-cli start", + "start:prod": "nitrostack-cli start", + "upgrade": "nitrostack-cli upgrade", + "install:all": "nitrostack-cli install", + "widget": "npm --prefix src/widgets" + }, + "dependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "@nitrostack/core": "^1", + "cors": "^2.8.6", + "dotenv": "^16.3.1", + "express": "^5.2.1", + "ws": "^8.21.1", + "zod": "^3.22.4" + }, + "devDependencies": { + "@nitrostack/cli": "^1", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/node": "^22.10.0", + "@types/ws": "^8.18.1", + "typescript": "^5.3.3" + }, + "author": "Lalthlamuana Darkim", + "nitrostack": { + "skillsVersion": "1.0.0" + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/api/context.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/api/context.ts new file mode 100644 index 000000000..ed8908b66 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/api/context.ts @@ -0,0 +1,18 @@ +import { SessionManager } from "../services/session-manager.js"; +import { DeviceRegistry } from "../services/device-registry.js"; +import { GuardianStateManager } from "../services/guardian-state-manager.js"; +import { GuardianCore } from "../services/guardian-core.js"; +import { GuardianWebSocketServer } from "../websocket/websocket-server.js"; + +export const sessionManager = new SessionManager(); +export const deviceRegistry = new DeviceRegistry(); +export const guardianStateManager = new GuardianStateManager(); + +export const websocketServer = new GuardianWebSocketServer(8080); + +export const guardianCore = new GuardianCore( + deviceRegistry, + sessionManager, + guardianStateManager, + websocketServer +); \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/api/server.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/api/server.ts new file mode 100644 index 000000000..200f874d5 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/api/server.ts @@ -0,0 +1,217 @@ +import { getAlert, getAlertHistory } from "../services/alert-state.js"; +import { getMonitorState } from "../services/monitor-state.js"; +import { + getLiveVitals, + getRespirationHistory, +} from "../services/live-state.js"; +import { getCsiRingBuffer, getLatestCsiAmplitudes } from "../services/csi-ring-buffer.js"; +import { packetRateTracker } from "../services/packet-rate-tracker.js"; +import express from "express"; +import cors from "cors"; + +import { + guardianCore, + deviceRegistry, + sessionManager, + guardianStateManager, + websocketServer +} from "./context.js"; + +const app = express(); + +app.use(cors()); +app.use(express.json()); + +/** + * GET /api/status + */ +app.get("/api/status", (req, res) => { + res.json(guardianStateManager.getState()); +}); + +/** + * GET /api/devices + */ +app.get("/api/devices", (req, res) => { + res.json(deviceRegistry.getAllDevices()); +}); + +/** + * GET /api/sessions + */ +app.get("/api/sessions", (req, res) => { + res.json(sessionManager.getAllSessions()); +}); + +/** + * POST /api/bridge + */ +app.post("/api/bridge", (req, res) => { + + try { + + const result = guardianCore.processBridgeMessage(req.body); + + res.json(result); + + } catch (err) { + + res.status(500).json({ + success: false, + error: err instanceof Error ? err.message : "Unknown error" + }); + + } + +}); +app.post("/api/device/register", (req, res) => { + + const { id, name } = req.body; + + if (!id || !name) { + return res.status(400).json({ + success: false, + message: "id and name are required" + }); + } + + const device = deviceRegistry.registerDevice(id, name); + + guardianStateManager.updateState({ + connectedDevices: deviceRegistry.getAllDevices().length + }); + + res.json({ + success: true, + device + }); + websocketServer.broadcast({ + event: "DEVICE_REGISTERED", + device +}); + +}); +app.post("/api/device/heartbeat", (req, res) => { + + const { id } = req.body; + + if (!id) { + return res.status(400).json({ + success: false, + message: "id is required" + }); + } + + deviceRegistry.updateHeartbeat(id); + + guardianStateManager.updateState({ + connectedDevices: deviceRegistry.getAllDevices().length + }); + + res.json({ + success: true + }); + websocketServer.broadcast({ + event: "DEVICE_HEARTBEAT", + id +}); + +}); + +const PORT = 5000; +app.post("/api/monitoring/start", (req, res) => { + + const { deviceId } = req.body; + + if (!deviceId) { + return res.status(400).json({ + success: false, + message: "deviceId is required" + }); + } + + const session = sessionManager.createSession(deviceId); + + guardianStateManager.updateState({ + monitoringActive: true, + activeSessions: sessionManager.getAllSessions().filter(s => s.monitoring).length + }); + + res.json({ + success: true, + session + }); + websocketServer.broadcast({ + event: "SESSION_STARTED", + session +}); + +}); +app.post("/api/monitoring/stop", (req, res) => { + + const { sessionId } = req.body; + + if (!sessionId) { + return res.status(400).json({ + success: false, + message: "sessionId is required" + }); + } + + const stopped = sessionManager.stopSession(sessionId); + + guardianStateManager.updateState({ + monitoringActive: false, + activeSessions: sessionManager.getAllSessions().filter(s => s.monitoring).length + }); + + res.json({ + success: stopped + }); + websocketServer.broadcast({ + event: "SESSION_STOPPED", + sessionId +}); + +}); +app.get("/api/live", (req, res) => { + res.json(getLiveVitals()); +}); +app.get("/api/live-history", (req, res) => { + res.json(getRespirationHistory()); +}); +app.get("/api/monitor", (req, res) => { + res.json(getMonitorState()); +}); +app.get("/api/alert", (req, res) => { + res.json(getAlert()); +}); +app.get("/api/alert/history", (req, res) => { + res.json(getAlertHistory()); +}); +app.get("/api/health", (req, res) => { + const state = guardianStateManager.getState(); + res.json({ + online: state.backendOnline, + websocketClients: websocketServer.clientCount, + connectedDevices: state.connectedDevices, + activeSessions: state.activeSessions, + monitoringActive: state.monitoringActive, + packetRate: packetRateTracker.getRate(), + uptimeSeconds: Math.round(process.uptime()), + }); +}); +app.get("/api/csi/latest", (req, res) => { + res.json({ amplitudes: getLatestCsiAmplitudes() }); +}); +app.get("/api/csi/history", (req, res) => { + res.json(getCsiRingBuffer()); +}); +app.get("/api/packet-rate", (req, res) => { + res.json({ packetRate: packetRateTracker.getRate() }); +}); +app.listen(PORT, () => { + + console.log(`Guardian REST API running on http://localhost:${PORT}`); + +}); \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/app.module.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/app.module.ts new file mode 100644 index 000000000..de8e2cc64 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/app.module.ts @@ -0,0 +1,35 @@ +import { McpApp, Module, ConfigModule } from '@nitrostack/core'; +import { CalculatorModule } from './modules/calculator/calculator.module.js'; +import { SystemHealthCheck } from './health/system.health.js'; +import { GuardianModule } from './modules/guardian/guardian.module.js'; +/** + * Root Application Module + * + * This is the main module that bootstraps the MCP server. + * It registers all feature modules and health checks. + */ +@McpApp({ + module: AppModule, + server: { + name: 'guardiansense-backend', + version: '1.0.0' + }, + logging: { + level: 'info' + } +}) +@Module({ + name: 'app', + description: 'Root application module', + imports: [ + ConfigModule.forRoot(), + CalculatorModule, + GuardianModule +], + providers: [ + // Health Checks + SystemHealthCheck, + ] +}) +export class AppModule {} + diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/health/system.health.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/health/system.health.ts new file mode 100644 index 000000000..5065cffb4 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/health/system.health.ts @@ -0,0 +1,55 @@ +import { HealthCheck, HealthCheckInterface, HealthCheckResult } from '@nitrostack/core'; + +/** + * System Health Check + * + * Monitors system resources and uptime + */ +@HealthCheck({ + name: 'system', + description: 'System resource and uptime check', + interval: 30 // Check every 30 seconds +}) +export class SystemHealthCheck implements HealthCheckInterface { + private startTime: number; + + constructor() { + this.startTime = Date.now(); + } + + async check(): Promise { + try { + const memoryUsage = process.memoryUsage(); + const uptime = Date.now() - this.startTime; + const uptimeSeconds = Math.floor(uptime / 1000); + + // Convert memory to MB + const memoryUsedMB = Math.round(memoryUsage.heapUsed / 1024 / 1024); + const memoryTotalMB = Math.round(memoryUsage.heapTotal / 1024 / 1024); + + // Consider unhealthy if memory usage is > 90% + const memoryPercent = (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100; + const isHealthy = memoryPercent < 90; + + return { + status: isHealthy ? 'up' : 'degraded', + message: isHealthy + ? 'System is healthy' + : 'High memory usage detected', + details: { + uptime: `${uptimeSeconds}s`, + memory: `${memoryUsedMB}MB / ${memoryTotalMB}MB (${Math.round(memoryPercent)}%)`, + pid: process.pid, + nodeVersion: process.version, + }, + }; + } catch (error: any) { + return { + status: 'down', + message: 'System health check failed', + details: error.message, + }; + } + } +} + diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/index.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/index.ts new file mode 100644 index 000000000..433dda1a7 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/index.ts @@ -0,0 +1,31 @@ +/** + * Calculator MCP Server + * + * Main entry point for the MCP server. + * Uses the @McpApp decorator pattern for clean, NestJS-style architecture. + * + * Transport Configuration: + * - Development (NODE_ENV=development): STDIO only + * - Production (NODE_ENV=production): Dual transport (STDIO + HTTP SSE) + */ + +import 'dotenv/config'; +import { McpApplicationFactory } from '@nitrostack/core'; +import { AppModule } from './app.module.js'; + +// Start Express REST API +import './api/server.js'; +/** + * Bootstrap the application + */ +async function bootstrap() { + // Create and start the MCP server + const server = await McpApplicationFactory.create(AppModule); + await server.start(); +} + +// Start the application +bootstrap().catch((error) => { + console.error('❌ Failed to start server:', error); + process.exit(1); +}); diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.module.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.module.ts new file mode 100644 index 000000000..9e4872fbb --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nitrostack/core'; +import { CalculatorTools } from './calculator.tools.js'; +import { CalculatorResources } from './calculator.resources.js'; +import { CalculatorPrompts } from './calculator.prompts.js'; + +@Module({ + name: 'calculator', + description: 'Basic arithmetic calculator', + controllers: [CalculatorTools, CalculatorResources, CalculatorPrompts] +}) +export class CalculatorModule {} + diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.prompts.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.prompts.ts new file mode 100644 index 000000000..ad9ae3505 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.prompts.ts @@ -0,0 +1,73 @@ +import { PromptDecorator as Prompt, ExecutionContext } from '@nitrostack/core'; + +export class CalculatorPrompts { + @Prompt({ + name: 'calculator_help', + description: 'Get help with calculator operations', + arguments: [ + { + name: 'operation', + description: 'The operation to get help with (optional)', + required: false + } + ] + }) + async getHelp(args: any, ctx: ExecutionContext) { + ctx.logger.info('Generating calculator help prompt'); + + const operation = args.operation; + + if (operation) { + // Help for specific operation + const helpText = this.getOperationHelp(operation); + return [ + { + role: 'user' as const, + content: `How do I use the ${operation} operation in the calculator?` + }, + { + role: 'assistant' as const, + content: helpText + } + ]; + } + + // General help + return [ + { + role: 'user' as const, + content: 'How do I use the calculator?' + }, + { + role: 'assistant' as const, + content: `The calculator supports four basic operations: + +1. **Addition** - Add two numbers together + Example: calculate(operation="add", a=5, b=3) = 8 + +2. **Subtraction** - Subtract one number from another + Example: calculate(operation="subtract", a=10, b=4) = 6 + +3. **Multiplication** - Multiply two numbers + Example: calculate(operation="multiply", a=6, b=7) = 42 + +4. **Division** - Divide one number by another + Example: calculate(operation="divide", a=20, b=5) = 4 + +Just call the 'calculate' tool with the operation and two numbers!` + } + ]; + } + + private getOperationHelp(operation: string): string { + const helps: Record = { + add: 'Use addition to sum two numbers. Call calculate(operation="add", a=5, b=3) to get 8.', + subtract: 'Use subtraction to find the difference. Call calculate(operation="subtract", a=10, b=4) to get 6.', + multiply: 'Use multiplication to find the product. Call calculate(operation="multiply", a=6, b=7) to get 42.', + divide: 'Use division to find the quotient. Call calculate(operation="divide", a=20, b=5) to get 4. Note: Cannot divide by zero!' + }; + + return helps[operation] || 'Unknown operation. Available operations: add, subtract, multiply, divide.'; + } +} + diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.resources.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.resources.ts new file mode 100644 index 000000000..77637251b --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.resources.ts @@ -0,0 +1,59 @@ +import { ResourceDecorator as Resource, Widget, ExecutionContext } from '@nitrostack/core'; + +export class CalculatorResources { + @Resource({ + uri: 'calculator://operations', + name: 'Calculator Operations', + description: 'List of available calculator operations', + mimeType: 'application/json', + examples: { + response: { + operations: [ + { name: 'add', symbol: '+', description: 'Addition' }, + { name: 'subtract', symbol: '-', description: 'Subtraction' }, + { name: 'multiply', symbol: '×', description: 'Multiplication' }, + { name: 'divide', symbol: '÷', description: 'Division' } + ] + } + } + }) + async getOperations(uri: string, ctx: ExecutionContext) { + ctx.logger.info('Fetching calculator operations'); + + const operations = [ + { + name: 'add', + symbol: '+', + description: 'Addition', + example: '5 + 3 = 8' + }, + { + name: 'subtract', + symbol: '-', + description: 'Subtraction', + example: '10 - 4 = 6' + }, + { + name: 'multiply', + symbol: '×', + description: 'Multiplication', + example: '6 × 7 = 42' + }, + { + name: 'divide', + symbol: '÷', + description: 'Division', + example: '20 ÷ 5 = 4' + } + ]; + + return { + contents: [{ + uri, + mimeType: 'application/json', + text: JSON.stringify({ operations }, null, 2) + }] + }; + } +} + diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.tools.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.tools.ts new file mode 100644 index 000000000..38ce863f5 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/calculator/calculator.tools.ts @@ -0,0 +1,166 @@ +import { ToolDecorator as Tool, Widget, ExecutionContext, z } from '@nitrostack/core'; +import * as fs from 'fs'; +import * as path from 'path'; + +export class CalculatorTools { + @Tool({ + name: 'calculate', + description: 'Perform basic arithmetic calculations', + inputSchema: z.object({ + operation: z.enum(['add', 'subtract', 'multiply', 'divide']).describe('The operation to perform'), + a: z.number().describe('First number'), + b: z.number().describe('Second number') + }), + examples: { + request: { + operation: 'add', + a: 5, + b: 3 + }, + response: { + operation: 'add', + a: 5, + b: 3, + result: 8, + expression: '5 + 3 = 8' + } + } + }) + @Widget('calculator-result') + async calculate(input: any, ctx: ExecutionContext) { + ctx.logger.info('Performing calculation', { + operation: input.operation, + a: input.a, + b: input.b + }); + + let result: number; + let symbol: string; + + switch (input.operation) { + case 'add': + result = input.a + input.b; + symbol = '+'; + break; + case 'subtract': + result = input.a - input.b; + symbol = '-'; + break; + case 'multiply': + result = input.a * input.b; + symbol = '×'; + break; + case 'divide': + if (input.b === 0) { + throw new Error('Cannot divide by zero'); + } + result = input.a / input.b; + symbol = '÷'; + break; + default: + throw new Error('Invalid operation'); + } + + return { + operation: input.operation, + a: input.a, + b: input.b, + result, + expression: `${input.a} ${symbol} ${input.b} = ${result}` + }; + } + + @Tool({ + name: 'convert_temperature', + description: 'Convert temperature units based on file content or direct input. Supports Celsius (C) and Fahrenheit (F).', + inputSchema: z.object({ + file_name: z.string().describe('Name of the uploaded file'), + file_type: z.string().describe('MIME type of the uploaded file'), + file_content: z.string().describe('Base64 encoded file content. Will be injected by system.'), + value: z.number().optional().describe('Temperature value to convert'), + from_unit: z.enum(['C', 'F']).optional().describe('Unit to convert from (C or F)'), + to_unit: z.enum(['C', 'F']).optional().describe('Unit to convert to (C or F)') + }) + }) + async convertTemperature(input: any, ctx: ExecutionContext) { + ctx.logger.info('Processing temperature file', { + name: input.file_name, + type: input.file_type, + value: input.value, + from: input.from_unit, + to: input.to_unit + }); + + // Save file to uploads directory + const uploadsDir = path.join(process.cwd(), 'uploads'); + if (!fs.existsSync(uploadsDir)) { + fs.mkdirSync(uploadsDir, { recursive: true }); + } + + const filePath = path.join(uploadsDir, input.file_name); + + // Decode base64 + if (input.file_content) { + try { + const matches = input.file_content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + let buffer; + + if (matches && matches.length === 3) { + buffer = Buffer.from(matches[2], 'base64'); + } else { + buffer = Buffer.from(input.file_content, 'base64'); + } + + fs.writeFileSync(filePath, buffer); + ctx.logger.info(`Saved file to ${filePath}`); + } catch (e) { + ctx.logger.error('Failed to save file', { error: e instanceof Error ? e.message : String(e) }); + } + } + + const fileStats = { + name: input.file_name, + type: input.file_type, + saved_path: filePath, + status: 'saved' + }; + + let result: number | null = null; + let message = `Successfully processed and saved file ${input.file_name}`; + + // Perform conversion logic + if (input.value !== undefined && input.from_unit && input.to_unit) { + try { + message += `. Converting ${input.value}°${input.from_unit} to ${input.to_unit}`; + + if (input.from_unit === input.to_unit) { + result = input.value; + } else if (input.from_unit === 'C' && input.to_unit === 'F') { + result = (input.value * 9 / 5) + 32; + } else if (input.from_unit === 'F' && input.to_unit === 'C') { + result = (input.value - 32) * 5 / 9; + } else { + throw new Error('Unsupported unit conversion'); + } + + // Round to 2 decimal places + if (result !== null) { + result = Math.round(result * 100) / 100; + message += `. Result: ${result}°${input.to_unit}`; + } + } catch (e: any) { + message += `. Conversion failed: ${e.message}`; + } + } else { + message += `. No valid conversion parameters detected from manual input or file extraction.`; + } + + return { + status: 'success', + message, + file_info: fileStats, + conversion_result: result !== null ? { value: result, unit: input.to_unit } : null, + original_value: input.value !== undefined ? { value: input.value, unit: input.from_unit } : null + }; + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.module.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.module.ts new file mode 100644 index 000000000..f78f18c25 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nitrostack/core'; + +import { GuardianTools } from './guardian.tools.js'; +import { GuardianResources } from './guardian.resources.js'; +import { GuardianPrompts } from './guardian.prompts.js'; + +@Module({ + name: 'guardian', + description: 'GuardianSense AI Backend', + controllers: [ + GuardianTools, + GuardianResources, + GuardianPrompts, + ], +}) +export class GuardianModule {} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.prompts.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.prompts.ts new file mode 100644 index 000000000..8a793ee7c --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.prompts.ts @@ -0,0 +1,33 @@ +import { PromptDecorator as Prompt, ExecutionContext } from '@nitrostack/core'; + +export class GuardianPrompts { + @Prompt({ + name: 'guardian_help', + description: 'Help the AI understand GuardianSense monitoring', + arguments: [] + }) + async getHelp(args: any, ctx: ExecutionContext) { + ctx.logger.info('Loading GuardianSense prompt'); + + return [ + { + role: 'user' as const, + content: 'How do I use GuardianSense?' + }, + { + role: 'assistant' as const, + content: `GuardianSense monitors a room using WiFi CSI data. + +Available capabilities include: + +• Start monitoring +• Stop monitoring +• View system status +• View connected devices +• View active monitoring sessions + +The AI should use GuardianSense tools whenever the user requests information about monitoring, breathing detection, movement detection, or device status.` + } + ]; + } +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.resources.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.resources.ts new file mode 100644 index 000000000..c7631f4be --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.resources.ts @@ -0,0 +1,40 @@ +import { ResourceDecorator as Resource, ExecutionContext } from '@nitrostack/core'; + +export class GuardianResources { + @Resource({ + uri: 'guardian://status', + name: 'Guardian System Status', + description: 'Current GuardianSense backend status', + mimeType: 'application/json', + examples: { + response: { + system: 'online', + monitoring: false, + activeSessions: 0, + connectedDevices: 0 + } + } + }) + async getStatus(uri: string, ctx: ExecutionContext) { + ctx.logger.info('Fetching GuardianSense system status'); + + return { + contents: [ + { + uri, + mimeType: 'application/json', + text: JSON.stringify( + { + system: 'online', + monitoring: false, + activeSessions: 0, + connectedDevices: 0 + }, + null, + 2 + ) + } + ] + }; + } +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.tools.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.tools.ts new file mode 100644 index 000000000..eac13d406 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/modules/guardian/guardian.tools.ts @@ -0,0 +1,72 @@ +import { ToolDecorator as Tool, ExecutionContext, z } from "@nitrostack/core"; +import { + sessionManager, + deviceRegistry, + guardianStateManager, +} from "../../api/context.js"; + +export class GuardianTools { + @Tool({ + name: "get_system_status", + description: "Returns GuardianSense backend status.", + inputSchema: z.object({}), + }) + async getSystemStatus(_input: unknown, ctx: ExecutionContext) { + ctx.logger.info("System status requested"); + return guardianStateManager.getState(); + } + + @Tool({ + name: "get_connected_devices", + description: "Returns all registered Guardian Bridge devices.", + inputSchema: z.object({}), + }) + async getConnectedDevices(_input: unknown, ctx: ExecutionContext) { + ctx.logger.info("Connected devices requested"); + return deviceRegistry.getAllDevices(); + } + + @Tool({ + name: "start_monitoring", + description: "Starts a GuardianSense monitoring session.", + inputSchema: z.object({ + deviceId: z.string().describe("Guardian Bridge device ID"), + }), + }) + async startMonitoring(input: { deviceId: string }, ctx: ExecutionContext) { + ctx.logger.info("Starting monitoring", { deviceId: input.deviceId }); + + const session = sessionManager.createSession(input.deviceId); + + guardianStateManager.updateState({ + monitoringActive: true, + activeSessions: sessionManager + .getAllSessions() + .filter((s) => s.monitoring).length, + }); + + return { success: true, session }; + } + + @Tool({ + name: "stop_monitoring", + description: "Stops an active GuardianSense monitoring session.", + inputSchema: z.object({ + sessionId: z.string().describe("Monitoring session ID"), + }), + }) + async stopMonitoring(input: { sessionId: string }, ctx: ExecutionContext) { + ctx.logger.info("Stopping monitoring", { sessionId: input.sessionId }); + + const success = sessionManager.stopSession(input.sessionId); + + guardianStateManager.updateState({ + monitoringActive: false, + activeSessions: sessionManager + .getAllSessions() + .filter((session) => session.monitoring).length, + }); + + return { success }; + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/alert-state.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/alert-state.ts new file mode 100644 index 000000000..bce85f467 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/alert-state.ts @@ -0,0 +1,107 @@ +export type Severity = "low" | "medium" | "high"; + +export interface AlertState { + active: boolean; + title: string; + message: string; + severity: Severity; + time: string; +} + +export interface AlertHistoryEntry extends AlertState { + raisedAt: number; +} + +const SEVERITY_ORDER: Record = { + low: 0, + medium: 1, + high: 2, +}; + +const MAX_HISTORY = 50; +const DOWNGRADE_COOLDOWN_MS = 10_000; + +let currentAlert: AlertState = { + active: false, + title: "", + message: "", + severity: "low", + time: "", +}; + +let history: AlertHistoryEntry[] = []; +let lastRaisedAt: number | null = null; + +function severityRank(severity: Severity): number { + return SEVERITY_ORDER[severity] ?? 0; +} + +/** + * Update the active alert. + * + * Escalations (higher or equal severity) take effect immediately and are + * recorded in history. Downgrades and clears are gated by a cooldown so a + * transient safe packet cannot instantly dismiss a raised alert. + */ +export function updateAlert(alert: AlertState): AlertState { + const now = Date.now(); + const incoming: AlertState = { + ...alert, + time: alert.time || new Date().toLocaleTimeString(), + }; + + if (!incoming.active) { + if ( + currentAlert.active && + lastRaisedAt !== null && + now - lastRaisedAt < DOWNGRADE_COOLDOWN_MS + ) { + return currentAlert; + } + currentAlert = incoming; + lastRaisedAt = null; + return currentAlert; + } + + const incomingRank = severityRank(incoming.severity); + const currentRank = severityRank(currentAlert.severity); + + if ( + currentAlert.active && + lastRaisedAt !== null && + now - lastRaisedAt < DOWNGRADE_COOLDOWN_MS && + incomingRank < currentRank + ) { + return currentAlert; + } + + if (incomingRank >= currentRank || !currentAlert.active) { + history.push({ ...incoming, raisedAt: now }); + if (history.length > MAX_HISTORY) { + history.shift(); + } + lastRaisedAt = now; + } + + currentAlert = incoming; + return currentAlert; +} + +export function getAlert(): AlertState { + return currentAlert; +} + +export function getAlertHistory(): AlertHistoryEntry[] { + return [...history]; +} + +export function resetAlert(): void { + currentAlert = { + active: false, + title: "", + message: "", + severity: "low", + time: "", + }; + lastRaisedAt = null; +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/baseline-engine.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/baseline-engine.ts new file mode 100644 index 000000000..34c2fbeaf --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/baseline-engine.ts @@ -0,0 +1,19 @@ +import { GuardianFeatures } from "./feature-extractor.js"; + +export class BaselineEngine { + + private baseline?: GuardianFeatures; + + learn(features: GuardianFeatures) { + + if (!this.baseline) { + this.baseline = features; + } + + } + + getBaseline() { + return this.baseline; + } + +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/csi-ring-buffer.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/csi-ring-buffer.ts new file mode 100644 index 000000000..c9d61db4f --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/csi-ring-buffer.ts @@ -0,0 +1,30 @@ +const MAX_SAMPLES = 60; + +export interface CsiSample { + timestamp: number; + amplitudes: number[]; +} + +const buffer: CsiSample[] = []; + +export function pushCsiSample(csi: number[]): void { + if (!csi || csi.length === 0) { + return; + } + + const amplitudes = csi.map((v) => Math.abs(v)); + buffer.push({ timestamp: Date.now(), amplitudes }); + + while (buffer.length > MAX_SAMPLES) { + buffer.shift(); + } +} + +export function getCsiRingBuffer(): CsiSample[] { + return [...buffer]; +} + +export function getLatestCsiAmplitudes(): number[] { + const latest = buffer[buffer.length - 1]; + return latest ? latest.amplitudes : []; +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/decision-engine.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/decision-engine.ts new file mode 100644 index 000000000..0dfea658b --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/decision-engine.ts @@ -0,0 +1,21 @@ +import { GuardianFeatures } from "./feature-extractor.js"; + +export interface Decision { + + breathingDetected: boolean; + movementDetected: boolean; + +} + +export class DecisionEngine { + + decide(features: GuardianFeatures): Decision { + + return { + breathingDetected: features.breathingRate > 2, + movementDetected: features.movementScore > 0.5 + }; + + } + +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/device-registry.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/device-registry.ts new file mode 100644 index 000000000..18beacea8 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/device-registry.ts @@ -0,0 +1,52 @@ +export interface GuardianDevice { + id: string; + name: string; + online: boolean; + lastSeen: Date; +} + +export class DeviceRegistry { + private devices = new Map(); + + registerDevice(id: string, name: string): GuardianDevice { + const device: GuardianDevice = { + id, + name, + online: true, + lastSeen: new Date(), + }; + + this.devices.set(id, device); + + return device; + } + + getDevice(id: string) { + return this.devices.get(id); + } + + getAllDevices() { + return [...this.devices.values()]; + } + + updateHeartbeat(id: string) { + const device = this.devices.get(id); + + if (!device) { + return; + } + + device.lastSeen = new Date(); + device.online = true; + } + + markOffline(id: string) { + const device = this.devices.get(id); + + if (!device) { + return; + } + + device.online = false; + } +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/dsp-engine.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/dsp-engine.ts new file mode 100644 index 000000000..197398160 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/dsp-engine.ts @@ -0,0 +1,32 @@ +export interface CsiPacket { + timestamp: string; + amplitude: number[]; + phase: number[]; +} + +export interface DspResult { + amplitudeMean: number; + phaseMean: number; + signalStrength: number; +} + +export class DSPEngine { + + process(packet: CsiPacket): DspResult { + + const amplitudeMean = + packet.amplitude.reduce((a, b) => a + b, 0) / + packet.amplitude.length; + + const phaseMean = + packet.phase.reduce((a, b) => a + b, 0) / + packet.phase.length; + + return { + amplitudeMean, + phaseMean, + signalStrength: amplitudeMean + }; + } + +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/feature-extractor.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/feature-extractor.ts new file mode 100644 index 000000000..403361272 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/feature-extractor.ts @@ -0,0 +1,19 @@ +import { DspResult } from "./dsp-engine.js"; + +export interface GuardianFeatures { + breathingRate: number; + movementScore: number; +} + +export class FeatureExtractor { + + extract(data: DspResult): GuardianFeatures { + + return { + breathingRate: data.amplitudeMean / 5, + movementScore: Math.abs(data.phaseMean) + }; + + } + +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-agent.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-agent.ts new file mode 100644 index 000000000..7dd20039d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-agent.ts @@ -0,0 +1,28 @@ +export class GuardianAgent { + + evaluate(breathing: boolean, movement: boolean) { + + if (!breathing && movement) { + return { + alert: true, + level: "warning", + message: "Movement detected without breathing." + }; + } + + if (!breathing && !movement) { + return { + alert: true, + level: "critical", + message: "No breathing or movement detected." + }; + } + + return { + alert: false, + level: "normal", + message: "Monitoring normally." + }; + } + +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-ai.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-ai.ts new file mode 100644 index 000000000..f6523272c --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-ai.ts @@ -0,0 +1,81 @@ +import { DSPEngine } from "./dsp-engine.js"; +import { FeatureExtractor } from "./feature-extractor.js"; +import { DecisionEngine } from "./decision-engine.js"; + +export interface GuardianAnalysis { + respiration: number; + motion: string; + confidence: number; + risk: string; + signalStrength: number; + breathingDetected: boolean; + movementDetected: boolean; +} + +export class GuardianAI { + private dsp = new DSPEngine(); + private features = new FeatureExtractor(); + private decisions = new DecisionEngine(); + + analyze(packet: any): GuardianAnalysis { + const csi: number[] = packet?.csi ?? []; + const rssi: number = packet?.rssi ?? -90; + + if (csi.length === 0) { + return { + respiration: 0, + motion: "Waiting...", + confidence: 0, + risk: "Unknown", + signalStrength: 0, + breathingDetected: false, + movementDetected: false, + }; + } + + const amplitudes = csi.map((v) => Math.abs(v)); + const phases = csi.map((v) => Math.atan2(0, v)); + + const dspResult = this.dsp.process({ + timestamp: new Date().toISOString(), + amplitude: amplitudes, + phase: phases, + }); + + const extracted = this.features.extract(dspResult); + const decision = this.decisions.decide(extracted); + + const respiration = Math.max( + 8, + Math.min(30, Math.round(extracted.breathingRate + 12)) + ); + + const motion = decision.movementDetected ? "Walking" : "Still"; + + const rssiFactor = Math.min(1, Math.max(0, (rssi + 90) / 40)); + const confidence = Math.min( + 99, + Math.max( + 55, + Math.round(60 + dspResult.signalStrength / 3 + rssiFactor * 20) + ) + ); + + let risk = "Safe"; + if (respiration < 10 || respiration > 25) { + risk = "High"; + } else if (decision.movementDetected) { + risk = "Low"; + } + + return { + respiration, + motion, + confidence, + risk, + signalStrength: dspResult.signalStrength, + breathingDetected: decision.breathingDetected, + movementDetected: decision.movementDetected, + }; + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-core.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-core.ts new file mode 100644 index 000000000..e4600869c --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-core.ts @@ -0,0 +1,117 @@ +import { DeviceRegistry } from "./device-registry.js"; +import { SessionManager } from "./session-manager.js"; +import { GuardianStateManager } from "./guardian-state-manager.js"; +import { GuardianAI, GuardianAnalysis } from "./guardian-ai.js"; +import { updateLiveVitals } from "./live-state.js"; +import { updateMonitorState } from "./monitor-state.js"; +import { Severity, updateAlert } from "./alert-state.js"; +import { packetRateTracker } from "./packet-rate-tracker.js"; +import { pushCsiSample } from "./csi-ring-buffer.js"; + +export interface GuardianBridgeMessage { + deviceId: string; + timestamp: string; + rawPacket: any; +} + +export interface LiveEventBroadcaster { + broadcast(data: unknown): void; +} + +const RISK_TO_SEVERITY: Record = { + High: "high", + Medium: "medium", + Low: "medium", + Safe: "low", +}; + +function analysisToSeverity(analysis: GuardianAnalysis): Severity { + const severity = RISK_TO_SEVERITY[analysis.risk]; + if (severity) { + return severity; + } + return analysis.movementDetected ? "medium" : "low"; +} + +export class GuardianCore { + private guardianAI = new GuardianAI(); + + constructor( + private deviceRegistry: DeviceRegistry, + private sessionManager: SessionManager, + private stateManager: GuardianStateManager, + private broadcaster: LiveEventBroadcaster + ) {} + + processBridgeMessage(message: GuardianBridgeMessage) { + const analysis = this.guardianAI.analyze(message.rawPacket); + const packetRate = packetRateTracker.record(); + const csi: number[] = message.rawPacket?.csi ?? []; + + pushCsiSample(csi); + + const severity = analysisToSeverity(analysis); + if (analysis.risk === "High" || analysis.movementDetected) { + updateAlert({ + active: true, + title: analysis.risk === "High" ? "High Risk Detected" : "Movement Detected", + message: + analysis.risk === "High" + ? "Respiration is outside the safe range." + : "Motion detected; monitoring elevated activity.", + severity, + time: new Date().toLocaleTimeString(), + }); + } else { + updateAlert({ + active: false, + title: "", + message: "", + severity, + time: "", + }); + } + + updateLiveVitals({ + respiration: analysis.respiration, + motion: analysis.motion, + confidence: analysis.confidence, + risk: analysis.risk, + csi, + }); + + updateMonitorState({ + packetRate, + rssi: message.rawPacket.rssi ?? -90, + activity: analysis.motion, + respiration: analysis.respiration, + confidence: analysis.confidence, + }); + + this.broadcaster.broadcast({ + event: "LIVE_UPDATE", + data: { + ...analysis, + csi, + packetRate, + rssi: message.rawPacket.rssi, + }, + }); + + this.deviceRegistry.updateHeartbeat(message.deviceId); + + this.stateManager.updateState({ + connectedDevices: this.deviceRegistry.getAllDevices().length, + activeSessions: this.sessionManager + .getAllSessions() + .filter((session) => session.monitoring).length, + }); + + return { + processed: true, + deviceId: message.deviceId, + analysis, + packetRate, + }; + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-state-manager.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-state-manager.ts new file mode 100644 index 000000000..aeeb3a302 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/guardian-state-manager.ts @@ -0,0 +1,35 @@ +export interface GuardianState { + backendOnline: boolean; + monitoringActive: boolean; + connectedDevices: number; + activeSessions: number; +} + +export class GuardianStateManager { + private state: GuardianState = { + backendOnline: true, + monitoringActive: false, + connectedDevices: 0, + activeSessions: 0, + }; + + getState(): GuardianState { + return this.state; + } + + updateState(update: Partial) { + this.state = { + ...this.state, + ...update, + }; + } + + reset() { + this.state = { + backendOnline: true, + monitoringActive: false, + connectedDevices: 0, + activeSessions: 0, + }; + } +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/live-state.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/live-state.ts new file mode 100644 index 000000000..b145b3ea1 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/live-state.ts @@ -0,0 +1,43 @@ +export interface LiveVitals { + respiration:number; + motion:string; + confidence:number; + risk:string; + csi:number[]; +} + +let latestVitals: LiveVitals = { + respiration:0, + motion:"Waiting...", + confidence:0, + risk:"Unknown", + csi: [], +}; + +// NEW: Store the last 30 respiration values +let respirationHistory: { + time: number; + respiration: number; +}[] = []; + +export function updateLiveVitals(vitals: LiveVitals) { + latestVitals = vitals; + + respirationHistory.push({ + time: Date.now(), + respiration: vitals.respiration, + }); + + if (respirationHistory.length > 30) { + respirationHistory.shift(); + } +} + +export function getLiveVitals() { + return latestVitals; +} + +// NEW +export function getRespirationHistory() { + return respirationHistory; +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/monitor-state.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/monitor-state.ts new file mode 100644 index 000000000..58d1d1599 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/monitor-state.ts @@ -0,0 +1,23 @@ +export interface MonitorState { + packetRate: number; + rssi: number; + activity: string; + respiration: number; + confidence: number; +} + +let latestMonitorState: MonitorState = { + packetRate: 0, + rssi: 0, + activity: "Waiting...", + respiration: 0, + confidence: 0, +}; + +export function updateMonitorState(state: MonitorState) { + latestMonitorState = state; +} + +export function getMonitorState() { + return latestMonitorState; +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/packet-rate-tracker.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/packet-rate-tracker.ts new file mode 100644 index 000000000..aac70fbb2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/packet-rate-tracker.ts @@ -0,0 +1,32 @@ +export class PacketRateTracker { + private timestamps: number[] = []; + private windowMs = 5000; + + record(): number { + const now = Date.now(); + this.timestamps.push(now); + this.prune(now); + return this.getRate(now); + } + + getRate(now: number = Date.now()): number { + this.prune(now); + if (this.timestamps.length < 2) { + return this.timestamps.length; + } + const elapsed = (now - this.timestamps[0]) / 1000; + if (elapsed <= 0) { + return 0; + } + return Math.round((this.timestamps.length / elapsed) * 10) / 10; + } + + private prune(now: number): void { + const cutoff = now - this.windowMs; + while (this.timestamps.length > 0 && this.timestamps[0] < cutoff) { + this.timestamps.shift(); + } + } +} + +export const packetRateTracker = new PacketRateTracker(); diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/session-manager.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/session-manager.ts new file mode 100644 index 000000000..5a803c9b2 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/services/session-manager.ts @@ -0,0 +1,43 @@ +export interface GuardianSession { + id: string; + deviceId: string; + startedAt: Date; + monitoring: boolean; +} + +export class SessionManager { + private sessions = new Map(); + + createSession(deviceId: string): GuardianSession { + const session: GuardianSession = { + id: crypto.randomUUID(), + deviceId, + startedAt: new Date(), + monitoring: true, + }; + + this.sessions.set(session.id, session); + + return session; + } + + getSession(id: string) { + return this.sessions.get(id); + } + + getAllSessions() { + return [...this.sessions.values()]; + } + + stopSession(id: string) { + const session = this.sessions.get(id); + + if (!session) { + return false; + } + + session.monitoring = false; + + return true; + } +} \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/websocket/websocket-server.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/websocket/websocket-server.ts new file mode 100644 index 000000000..59e77b1af --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/websocket/websocket-server.ts @@ -0,0 +1,59 @@ +import { WebSocketServer, WebSocket } from "ws"; + +export class GuardianWebSocketServer { + private wss: WebSocketServer; + private heartbeatTimer: ReturnType | null = null; + + constructor(port = 8080) { + this.wss = new WebSocketServer({ port }); + + this.wss.on("connection", (socket) => { + socket.on("error", (err) => { + console.error(`WebSocket error: ${err.message}`); + }); + }); + + this.heartbeatTimer = setInterval(() => this.pruneDeadClients(), 30_000); + this.heartbeatTimer.unref?.(); + + console.log(`Guardian WebSocket running on ws://localhost:${port}`); + } + + broadcast(data: unknown) { + const message = JSON.stringify(data); + + this.wss.clients.forEach((client) => { + if (client.readyState !== WebSocket.OPEN) { + return; + } + try { + client.send(message); + } catch (err) { + console.error(`WebSocket send error: ${(err as Error).message}`); + } + }); + } + + private pruneDeadClients() { + for (const client of this.wss.clients) { + if (client.readyState !== WebSocket.OPEN) { + client.terminate(); + } + } + } + + get clientCount(): number { + return this.wss.clients.size; + } + + close() { + if (this.heartbeatTimer) { + clearInterval(this.heartbeatTimer); + this.heartbeatTimer = null; + } + for (const client of this.wss.clients) { + client.terminate(); + } + this.wss.close(); + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/app/calculator-result/page.tsx b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/app/calculator-result/page.tsx new file mode 100644 index 000000000..6d9859ee1 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/app/calculator-result/page.tsx @@ -0,0 +1,180 @@ +'use client'; + +import { useTheme, useWidgetState, useWidgetSDK } from '@nitrostack/widgets'; + +/** + * Example widget demonstrating NitroStack Widget SDK + * This widget is fully compatible with OpenAI ChatGPT + */ + +interface CalculatorData { + operation: string; + a: number; + b: number; + result: number; + expression: string; +} + +export default function CalculatorResult() { + // Use Widget SDK hooks + const theme = useTheme(); + const { getToolOutput } = useWidgetSDK(); + const [state, setState] = useWidgetState<{ viewMode: 'compact' | 'detailed' }>(() => ({ + viewMode: 'detailed' + })); + + // Access tool output from Widget SDK + const data = getToolOutput(); + + if (!data) { + return ( +
+ Loading... +
+ ); + } + + const getOperationColor = (op: string) => { + const colors: Record = { + add: '#10b981', + subtract: '#f59e0b', + multiply: '#3b82f6', + divide: '#8b5cf6' + }; + return colors[op] || '#6b7280'; + }; + + const getOperationIcon = (op: string) => { + const icons: Record = { + add: '➕', + subtract: '➖', + multiply: '✖️', + divide: '➗' + }; + return icons[op] || '🔢'; + }; + + const isDark = theme === 'dark'; + const bgColor = isDark ? '#1a1a1a' : '#ffffff'; + const textColor = isDark ? '#ffffff' : '#000000'; + const mutedColor = isDark ? 'rgba(255,255,255,0.6)' : 'rgba(0,0,0,0.6)'; + + return ( +
+
+
+ + {getOperationIcon(data.operation)} + +
+

+ Calculator Result +

+

+ {data.operation.charAt(0).toUpperCase() + data.operation.slice(1)} +

+
+
+ + {/* View mode toggle */} + +
+ +
+
+ {data.expression} +
+ + {state?.viewMode === 'detailed' && ( +
+
+
First
+
{data.a}
+
+
+
Second
+
{data.b}
+
+
+
Result
+
+ {data.result} +
+
+
+ )} +
+ +
+ ✨ NitroStack Calculator + + Theme: {theme || 'light'} | Mode: {state?.viewMode || 'detailed'} + +
+
+ ); +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/app/layout.tsx b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/app/layout.tsx new file mode 100644 index 000000000..40b03ef08 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/app/layout.tsx @@ -0,0 +1,18 @@ +'use client'; + +import { WidgetLayout } from '@nitrostack/widgets'; + + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/next-env.d.ts b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/next-env.d.ts new file mode 100644 index 000000000..40c3d6809 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/next.config.js b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/next.config.js new file mode 100644 index 000000000..f35620cee --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/next.config.js @@ -0,0 +1,45 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + transpilePackages: ['nitrostack'], + + // Static export for production builds + ...(process.env.NODE_ENV === 'production' && { + output: 'export', + distDir: 'out', + images: { + unoptimized: true, + }, + }), + + // Development optimizations to prevent cache corruption + ...(process.env.NODE_ENV === 'development' && { + // Use memory cache instead of filesystem cache in dev to avoid stale chunks + webpack: (config, { isServer }) => { + // Disable persistent caching in development to prevent chunk reference errors + if (config.cache && config.cache.type === 'filesystem') { + config.cache = { + type: 'memory', + }; + } + + // Improve cache busting for new files + if (!isServer) { + config.cache = false; // Disable cache completely on client in dev + } + + return config; + }, + + // Disable build activity indicator which can cause issues + devIndicators: { + buildActivity: false, + buildActivityPosition: 'bottom-right', + }, + + // Faster dev server + compress: false, + }), +}; + +export default nextConfig; diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/package-lock.json b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/package-lock.json new file mode 100644 index 000000000..0fc5cf3a9 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/package-lock.json @@ -0,0 +1,2527 @@ +{ + "name": "calculator-widgets", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "calculator-widgets", + "version": "1.0.0", + "dependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "@nitrostack/core": "^1.0.14", + "@nitrostack/widgets": "^1", + "next": "^14.2.5", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@hono/node-server": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", + "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.5.tgz", + "integrity": "sha512-TjPH2S2y5UEGKhmI6+XGFuqfqOV4ppe1x6DA3txnUaEWkgtA4G5vo14jGKFZmegdkZ1H4QMLyujLvoU1BEdnAg==", + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "dependencies": { + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@next/env": { + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", + "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nitrostack/core": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@nitrostack/core/-/core-1.0.14.tgz", + "integrity": "sha512-FfG5rOxZwAztHiwPqRPj3xjgoiiPa1A06y2BBqGRlNAkK8R5izImD/f3zhvo1OrGKtoDC/qEw2iykKK24UR/FA==", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.4", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "express": "^4.21.2", + "jose": "^6.1.0", + "jsonwebtoken": "^9.0.2", + "reflect-metadata": "^0.2.1", + "uuid": "^11.0.5", + "winston": "^3.17.0", + "ws": "^8.18.3", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0" + } + }, + "node_modules/@nitrostack/core/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@nitrostack/core/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/@nitrostack/core/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nitrostack/core/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@nitrostack/core/node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nitrostack/core/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nitrostack/core/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nitrostack/core/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nitrostack/core/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/@nitrostack/core/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nitrostack/core/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nitrostack/core/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nitrostack/core/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nitrostack/core/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@nitrostack/widgets": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@nitrostack/widgets/-/widgets-1.0.8.tgz", + "integrity": "sha512-/E8MAgOp/rYOyRNydzamxsJAhsBp7Q9A8LYB7m+kqy+o0ubLBsU7rb1EIaIg8JX8KPdxfchxqwo0HxzUWRsYDg==", + "license": "Apache-2.0", + "peerDependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.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/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "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/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "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==", + "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/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "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/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz", + "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "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==", + "license": "ISC" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.33", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.33.tgz", + "integrity": "sha512-+SwvkaiJtxsiPjhy9LivY/1m7UsNqCJetM1BrZl9A5DkQhlbHQDU730mMiDPWjnoCYOM8Chf3WrCJw27kNTPFQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.5.tgz", + "integrity": "sha512-2E5L2yRp03FnwreJLJX8/r7mHiZICCf8kG7fAsTWkSQTDAcc46NIZoQLKy+EJ8sPoJlxyS4OQR5H70LjIZZlIQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "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==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next": { + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", + "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.35", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.33", + "@next/swc-darwin-x64": "14.2.33", + "@next/swc-linux-arm64-gnu": "14.2.33", + "@next/swc-linux-arm64-musl": "14.2.33", + "@next/swc-linux-x64-gnu": "14.2.33", + "@next/swc-linux-x64-musl": "14.2.33", + "@next/swc-win32-arm64-msvc": "14.2.33", + "@next/swc-win32-ia32-msvc": "14.2.33", + "@next/swc-win32-x64-msvc": "14.2.33" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "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==", + "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/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/package.json b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/package.json new file mode 100644 index 000000000..23d02efa7 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/package.json @@ -0,0 +1,25 @@ +{ + "name": "calculator-widgets", + "version": "1.0.0", + "type": "module", + "private": true, + "scripts": { + "dev": "next dev -p 3001 --port 3001", + "build": "next build", + "start": "next start -p 3001" + }, + "dependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "@nitrostack/core": "^1.0.14", + "@nitrostack/widgets": "^1", + "next": "^14.2.5", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5" + } +} diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/tsconfig.json b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/tsconfig.json new file mode 100644 index 000000000..2c0ad665d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} + diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/widget-manifest.json b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/widget-manifest.json new file mode 100644 index 000000000..1fc23e563 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/src/widgets/widget-manifest.json @@ -0,0 +1,48 @@ +{ + "version": "1.0.0", + "widgets": [ + { + "uri": "/calculator-result", + "name": "Calculator Result", + "description": "Displays the result of a calculation with operation details", + "examples": [ + { + "name": "Addition Example", + "description": "Shows the result of adding 5 + 3", + "data": { + "operation": "add", + "a": 5, + "b": 3, + "result": 8, + "expression": "5 + 3 = 8" + } + }, + { + "name": "Multiplication Example", + "description": "Shows the result of multiplying 6 × 7", + "data": { + "operation": "multiply", + "a": 6, + "b": 7, + "result": 42, + "expression": "6 × 7 = 42" + } + }, + { + "name": "Division Example", + "description": "Shows the result of dividing 20 ÷ 4", + "data": { + "operation": "divide", + "a": 20, + "b": 4, + "result": 5, + "expression": "20 ÷ 4 = 5" + } + } + ], + "tags": ["calculator", "math", "result"] + } + ], + "generatedAt": "2025-01-01T00:00:00.000Z" +} + diff --git a/sample-apps/Ctrl Z/backend/GuardianSenseBackend/tsconfig.json b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/tsconfig.json new file mode 100644 index 000000000..c9ccf80ef --- /dev/null +++ b/sample-apps/Ctrl Z/backend/GuardianSenseBackend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "node", + "rootDir": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "src/widgets"] +} + + diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/Readme.md b/sample-apps/Ctrl Z/backend/guardian_bridge/Readme.md new file mode 100644 index 000000000..943abb959 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/Readme.md @@ -0,0 +1,264 @@ +# Guardian Bridge + +Guardian Bridge is the hardware communication layer of the GuardianSense project. It is responsible for receiving CSI (Channel State Information) packets from the ESP32-S3 receiver, validating and parsing them, and saving them as structured JSON datasets for AI training or forwarding them to the GuardianSense backend. + +--- + +# Responsibilities + +Guardian Bridge is responsible for: + +- Connecting to the ESP32-S3 Receiver +- Reading CSI packets over Serial +- Validating incoming packets +- Parsing CSI data +- Recording datasets +- Writing structured JSONL files +- Automatically reconnecting to the receiver if disconnected + +Guardian Bridge **does not** perform: + +- AI inference +- Fall detection logic +- Backend state management +- Dashboard rendering +- Device management + +Those responsibilities belong to the GuardianSense backend. + +--- + +# Project Structure + +``` +guardian_bridge/ + +├── config.py # Configuration (COM port, baud rate, logging, USB IDs) +├── logger_config.py # Console + rotating file logging setup +├── port_detector.py # Automatic ESP32 COM port discovery (VID/PID prioritized) +├── serial_manager.py # Serial communication with ESP32-S3 +├── packet_validator.py # Filters valid CSI packets +├── csi_parser.py # Converts raw CSI into structured Python objects +├── binary_parser.py # Parses raw binary CSI frames (Phase 4) +├── api_client.py # Forwards parsed packets to GuardianSense backend +├── dataset_writer.py # Writes packets into JSONL datasets +├── runtime.py # Main Guardian Runtime (reconnect + health aware) +├── recorder.py # Dataset recording tool +├── main.py # Runtime entry point +├── health_metrics.py # Runtime health/metrics collection +├── health_server.py # Optional HTTP health endpoint +├── test_bridge.py # Unit tests (parsers, validator, queue, API retry) +├── test_runtime.py # Runtime integration tests +├── requirements.txt # Python dependencies +└── README.md +``` + +--- + +# Hardware Required + +- ESP32 Sender +- ESP32-S3 Receiver +- USB Cable(s) +- Windows / Linux / macOS +- Python 3.11+ + +--- + +# Installation + +Install dependencies: + +```bash +pip install -r requirements.txt +``` + +--- + +# Configuration + +Edit `config.py` if necessary. + +Example: + +```python +SERIAL_PORT = "COM5" +BAUD_RATE = 921600 +``` + +--- + +# Recording a Dataset + +Run: + +```bash +python recorder.py +``` + +The recorder will ask for: + +- Activity +- Recording duration + +Example: + +``` +1. Walking +2. Sitting +3. Standing +4. Breathing +5. Fall +6. Empty Room +``` + +Recorded datasets are automatically saved to: + +``` +datasets/raw/ +``` + +Example: + +``` +datasets/raw/ + walking/ + walking_001.jsonl + walking_002.jsonl + + sitting/ + sitting_001.jsonl +``` + +--- + +# Running Guardian Runtime + +Run: + +```bash +python main.py +``` + +Guardian Runtime will: + +- Search for ESP32-S3 Receiver (auto-detect COM port) +- Connect automatically +- Receive CSI packets +- Validate packets +- Parse packets +- Forward packets to the backend and/or save datasets +- Detect serial disconnection and reconnect automatically +- Continue until stopped + +--- + +# Running Tests + +```bash +python -m unittest test_bridge -v +python -m unittest test_runtime -v +``` + +`test_bridge.py` covers both CSI schema variants (ESP32-S3 / C5-C6), packet validation, binary frame parsing, the drop-aware queue, and the API client retry policy. + +--- + +# Data Flow + +``` +ESP32 Sender + │ + ▼ +ESP32-S3 Receiver + │ + ▼ +SerialManager + │ + ▼ +PacketValidator + │ + ▼ +CSIParser + │ + ▼ +ApiClient ───────► POST /api/bridge (GuardianSense backend) + │ + ▼ +DatasetWriter + │ + ▼ +JSONL Dataset +``` + +--- + +# Output Format + +Each packet is stored as one JSON object per line. + +Example: + +```json +{ + "packet_type": "CSI_DATA", + "timestamp": 107635, + "mac": "1a:00:00:00:00:00", + "rssi": -26, + "channel": 11, + "csi": [0, 0, -5, 7, ...] +} +``` + +--- + +# Backend Integration + +Guardian Bridge forwards parsed CSI packets to the GuardianSense backend. + +Pipeline: + +``` +ESP32 + │ + ▼ +Guardian Bridge + │ +POST /api/bridge + │ +Guardian Core + │ +Guardian Agent + │ +Frontend Dashboard +``` + +Guardian Bridge remains responsible only for hardware communication and dataset generation. + +--- + +# Version + +Current Version: + +**Guardian Bridge v1.1** + +Status: + +✅ Stable + +Completed Features: + +- Serial Communication +- Auto Reconnect +- Auto COM Port Detection +- Runtime Manager +- Dataset Recorder +- Packet Validation +- CSI Parsing +- JSONL Dataset Generation +- Backend API Forwarding +- Rotating Logging +- Runtime Health Endpoint +- Binary Frame Parsing +- Unit Test Suite \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/api_client.py b/sample-apps/Ctrl Z/backend/guardian_bridge/api_client.py new file mode 100644 index 000000000..5a21f43fd --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/api_client.py @@ -0,0 +1,121 @@ +import random +import time +from concurrent.futures import ThreadPoolExecutor +from datetime import datetime, timezone + +import requests + +from config import ( + BASE_URL, + DEVICE_ID, + DEVICE_NAME, + HTTP_MAX_RETRIES, + HTTP_TIMEOUT, + HTTP_WORKERS, +) +from logger_config import setup_logging + +logger = setup_logging(__name__) + +_RETRYABLE_STATUS = {408, 429, 500, 502, 503, 504} + + +def _request_with_retry(method: str, url: str, **kwargs) -> requests.Response | None: + kwargs.setdefault("timeout", HTTP_TIMEOUT) + + for attempt in range(1, HTTP_MAX_RETRIES + 1): + try: + response = requests.request(method, url, **kwargs) + except requests.RequestException as exc: + logger.warning( + "HTTP %s %s failed (attempt %d/%d): %s", + method, + url, + attempt, + HTTP_MAX_RETRIES, + exc, + ) + else: + if response.ok: + return response + + if response.status_code in _RETRYABLE_STATUS: + logger.warning( + "HTTP %s %s returned %d (attempt %d/%d)", + method, + url, + response.status_code, + attempt, + HTTP_MAX_RETRIES, + ) + else: + # Client error: retrying will not help. + logger.error( + "HTTP %s %s returned non-retryable %d: %s", + method, + url, + response.status_code, + response.text[:500], + ) + return response + + if attempt < HTTP_MAX_RETRIES: + delay = min(0.5 * (2 ** (attempt - 1)), 5.0) + time.sleep(delay * (0.8 + 0.4 * random.random())) + + logger.error("HTTP request exhausted retries: %s %s", method, url) + return None + + +def register_device() -> bool: + payload = {"id": DEVICE_ID, "name": DEVICE_NAME} + response = _request_with_retry( + "POST", f"{BASE_URL}/device/register", json=payload + ) + if response: + logger.info("Device registered: %s", response.json()) + return True + return False + + +def start_monitoring() -> bool: + payload = {"deviceId": DEVICE_ID} + response = _request_with_retry( + "POST", f"{BASE_URL}/monitoring/start", json=payload + ) + if response: + logger.info("Monitoring started: %s", response.json()) + return True + return False + + +def send_packet(packet: dict) -> bool: + payload = { + "deviceId": DEVICE_ID, + "timestamp": datetime.now(timezone.utc).isoformat(), + "rawPacket": packet, + } + response = _request_with_retry( + "POST", f"{BASE_URL}/bridge", json=payload + ) + return response is not None + + +class HttpSender: + """Background HTTP sender with a worker pool.""" + + def __init__(self, on_error=None): + self._executor = ThreadPoolExecutor( + max_workers=HTTP_WORKERS, thread_name_prefix="http-sender" + ) + self._on_error = on_error + + def submit(self, packet: dict) -> None: + self._executor.submit(self._send, packet) + + def _send(self, packet: dict) -> None: + if not send_packet(packet) and self._on_error: + self._on_error() + + def shutdown(self, wait: bool = True) -> None: + self._executor.shutdown(wait=wait) diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/binary_parser.py b/sample-apps/Ctrl Z/backend/guardian_bridge/binary_parser.py new file mode 100644 index 000000000..891e8ce4e --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/binary_parser.py @@ -0,0 +1,90 @@ +"""Binary CSI frame decoder for compact UART protocol (Phase 4).""" + +import struct + +from logger_config import setup_logging + +logger = setup_logging(__name__) + +MAGIC = b"GS" +VERSION = 1 +HEADER_FMT = "<2sB i b B b B b H I" # magic, ver, id, rssi, ch, noise, agc, fft, csi_len, ts +HEADER_SIZE = struct.calcsize(HEADER_FMT) +CHECKSUM_SIZE = 2 + + +class BinaryCSIParser: + """Parse compact binary CSI frames emitted by updated firmware.""" + + def __init__(self): + self._buffer = bytearray() + + def feed(self, data: bytes) -> list[dict]: + """Append bytes and return all complete packets found.""" + self._buffer.extend(data) + packets: list[dict] = [] + + while True: + packet = self._try_extract_one() + if packet is None: + break + packets.append(packet) + + return packets + + def _try_extract_one(self) -> dict | None: + buf = self._buffer + + # Resync to magic bytes + while len(buf) >= 2: + if buf[0:2] == MAGIC: + break + del buf[0] + + if len(buf) < HEADER_SIZE + CHECKSUM_SIZE: + return None + + header = struct.unpack_from(HEADER_FMT, buf, 0) + magic, version, rx_id, rssi, channel, noise_floor, agc_gain, fft_gain, csi_len, timestamp = header + + if magic != MAGIC or version != VERSION: + del buf[0] + return None + + frame_size = HEADER_SIZE + (csi_len * 2) + CHECKSUM_SIZE + if len(buf) < frame_size: + return None + + csi_start = HEADER_SIZE + csi_end = csi_start + (csi_len * 2) + csi_bytes = buf[csi_start:csi_end] + csi = list(struct.unpack(f"<{csi_len}h", csi_bytes)) + + checksum_expected = struct.unpack_from(" int | None: + try: + number = int(value) + except (TypeError, ValueError): + logger.warning("%s is not an integer: %r", field_name, value) + return None + if number < minimum or number > maximum: + logger.warning( + "%s out of range %d..%d: %d", + field_name, + minimum, + maximum, + number, + ) + return None + return number + + @classmethod + def _validate_csi_array(cls, raw: object) -> list[int] | None: + if not isinstance(raw, list): + logger.warning("CSI data is not a list") + return None + + if not raw: + logger.warning("Empty CSI array") + return None + + if len(raw) > MAX_CSI_LEN: + logger.warning( + "CSI array too large: %d (max %d)", len(raw), MAX_CSI_LEN + ) + return None + + csi: list[int] = [] + for value in raw: + if isinstance(value, bool) or not isinstance(value, int): + logger.warning("CSI value is not an integer: %r", value) + return None + if value < CSI_VALUE_MIN or value > CSI_VALUE_MAX: + logger.warning( + "CSI value out of int16 range %d..%d: %d", + CSI_VALUE_MIN, + CSI_VALUE_MAX, + value, + ) + return None + csi.append(value) + return csi + + @staticmethod + def parse(line: str | bytes) -> dict | None: + try: + if isinstance(line, bytes): + line = line.decode("utf-8", errors="ignore") + + line = line.strip() + if not line.startswith("CSI_DATA"): + return None + + reader = csv.reader(StringIO(line)) + fields = next(reader) + + schema = SCHEMAS.get(len(fields)) + if schema is None: + logger.warning( + "Unexpected field count %d (expected %s)", + len(fields), + list(SCHEMAS.keys()), + ) + return None + + if not MAC_PATTERN.match(fields[2]): + logger.warning("Invalid MAC address: %r", fields[2]) + return None + + try: + csi_len = int(fields[-3]) + csi_raw = json.loads(fields[-1]) + except (ValueError, json.JSONDecodeError) as exc: + logger.warning("CSI array parse failed: %s", exc) + return None + + if csi_len != len(csi_raw): + logger.warning( + "CSI length mismatch: header=%d array=%d", + csi_len, + len(csi_raw), + ) + return None + + csi = CSIParser._validate_csi_array(csi_raw) + if csi is None: + return None + + record = dict(zip(schema, fields)) + + rssi = CSIParser._valid_int(record["rssi"], "rssi", _RSSI_MIN, _RSSI_MAX) + channel = CSIParser._valid_int( + record["channel"], "channel", _CHANNEL_MIN, _CHANNEL_MAX + ) + rate = CSIParser._valid_int(record["rate"], "rate", _RATE_MIN, _RATE_MAX) + if rssi is None or channel is None or rate is None: + return None + + packet = { + "packet_type": record["type"], + "id": CSIParser._valid_int(record["id"], "id", 0, 2**31 - 1), + "mac": record["mac"], + "rssi": rssi, + "rate": rate, + "channel": channel, + "noise_floor": CSIParser._valid_int( + record["noise_floor"], "noise_floor", -200, 200 + ), + "sig_len": CSIParser._valid_int( + record["sig_len"], "sig_len", 0, MAX_CSI_LEN + ), + "len": CSIParser._valid_int(record["len"], "len", 0, MAX_CSI_LEN), + "first_word": CSIParser._valid_int( + record["first_word"], "first_word", 0, 1 + ), + "local_timestamp": CSIParser._valid_int( + record["local_timestamp"], "local_timestamp", 0, 2**63 - 1 + ), + "csi": csi, + "schema": "s3" if len(fields) == len(DATA_COLUMNS_S3) else "c5c6", + } + + if any(value is None for value in packet.values()): + logger.warning("Packet rejected: missing/out-of-range field") + return None + + if len(fields) == len(DATA_COLUMNS_C5C6): + packet["agc_gain"] = CSIParser._valid_int( + record["agc_gain"], "agc_gain", 0, 255 + ) + packet["fft_gain"] = CSIParser._valid_int( + record["fft_gain"], "fft_gain", -128, 127 + ) + else: + packet["secondary_channel"] = CSIParser._valid_int( + record["secondary_channel"], "secondary_channel", 0, 1 + ) + packet["sig_mode"] = CSIParser._valid_int( + record["sig_mode"], "sig_mode", 0, 3 + ) + packet["mcs"] = CSIParser._valid_int(record["mcs"], "mcs", 0, 31) + packet["bandwidth"] = CSIParser._valid_int( + record["bandwidth"], "bandwidth", 0, 3 + ) + + return packet + + except Exception as exc: + logger.error("Parser error: %s", exc, exc_info=DEBUG) + return None diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/dataset_writer.py b/sample-apps/Ctrl Z/backend/guardian_bridge/dataset_writer.py new file mode 100644 index 000000000..10d40696f --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/dataset_writer.py @@ -0,0 +1,58 @@ +import json +import threading +import time +from pathlib import Path + +from config import WRITE_BATCH_SIZE, WRITE_FLUSH_INTERVAL +from logger_config import setup_logging + +logger = setup_logging(__name__) + + +class DatasetWriter: + """Buffered, thread-safe JSONL writer.""" + + def __init__(self, output_path: str): + self.output_path = Path(output_path) + self.output_path.parent.mkdir(parents=True, exist_ok=True) + self._file = open(self.output_path, "a", buffering=8192) + self._lock = threading.Lock() + self._batch: list[dict] = [] + self._last_flush = time.time() + logger.info("Dataset writer opened: %s", self.output_path) + + def save(self, packet: dict) -> None: + self.save_batch([packet]) + + def save_batch(self, packets: list[dict]) -> None: + if not packets: + return + + with self._lock: + self._batch.extend(packets) + should_flush = ( + len(self._batch) >= WRITE_BATCH_SIZE + or (time.time() - self._last_flush) >= WRITE_FLUSH_INTERVAL + ) + if should_flush: + self._flush_locked() + + def flush(self) -> None: + with self._lock: + self._flush_locked() + + def _flush_locked(self) -> None: + if not self._batch: + return + lines = "".join(json.dumps(p) + "\n" for p in self._batch) + self._file.write(lines) + self._file.flush() + logger.debug("Flushed %d packets to disk", len(self._batch)) + self._batch.clear() + self._last_flush = time.time() + + def close(self) -> None: + with self._lock: + self._flush_locked() + self._file.close() + logger.info("Dataset writer closed") diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/guardian_bridge.py b/sample-apps/Ctrl Z/backend/guardian_bridge/guardian_bridge.py new file mode 100644 index 000000000..702f94339 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/guardian_bridge.py @@ -0,0 +1,25 @@ +import serial + +SERIAL_PORT = "COM5" +BAUD_RATE = 921600 + +print("Opening serial port...") + +ser = serial.Serial( + SERIAL_PORT, + BAUD_RATE, + timeout=0.1 +) + +print("Connected!") + +while True: + try: + data = ser.read(4096) + + if data: + print(repr(data)) + + except KeyboardInterrupt: + print("Stopped.") + break \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/health_metrics.py b/sample-apps/Ctrl Z/backend/guardian_bridge/health_metrics.py new file mode 100644 index 000000000..800c43171 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/health_metrics.py @@ -0,0 +1,89 @@ +import queue +import threading +import time +from dataclasses import dataclass, field + +from logger_config import setup_logging + +logger = setup_logging(__name__) + + +@dataclass +class HealthMetrics: + packets_read: int = 0 + packets_parsed: int = 0 + packets_written: int = 0 + packets_sent: int = 0 + parse_errors: int = 0 + validation_errors: int = 0 + http_errors: int = 0 + raw_queue_drops: int = 0 + packet_queue_drops: int = 0 + serial_reconnects: int = 0 + started_at: float = field(default_factory=time.time) + + def to_dict(self) -> dict: + elapsed = max(time.time() - self.started_at, 0.001) + return { + "uptime_seconds": round(elapsed, 1), + "packets_read": self.packets_read, + "packets_parsed": self.packets_parsed, + "packets_written": self.packets_written, + "packets_sent": self.packets_sent, + "parse_errors": self.parse_errors, + "validation_errors": self.validation_errors, + "http_errors": self.http_errors, + "raw_queue_drops": self.raw_queue_drops, + "packet_queue_drops": self.packet_queue_drops, + "serial_reconnects": self.serial_reconnects, + "read_rate": round(self.packets_read / elapsed, 2), + "parse_rate": round(self.packets_parsed / elapsed, 2), + } + + +class MetricsTracker: + """Thread-safe wrapper around HealthMetrics.""" + + def __init__(self): + self._metrics = HealthMetrics() + self._lock = threading.Lock() + + def increment(self, field_name: str, amount: int = 1) -> None: + with self._lock: + current = getattr(self._metrics, field_name, 0) + setattr(self._metrics, field_name, current + amount) + + def snapshot(self) -> dict: + with self._lock: + return self._metrics.to_dict() + + +class DropAwareQueue: + """Bounded queue that tracks drops when full.""" + + def __init__( + self, + maxsize: int, + metrics: MetricsTracker, + drop_field: str, + ): + self._queue: queue.Queue = queue.Queue(maxsize=maxsize) + self._metrics = metrics + self._drop_field = drop_field + + def put(self, item, block: bool = True, timeout: float | None = None) -> bool: + try: + self._queue.put(item, block=block, timeout=timeout) + return True + except queue.Full: + self._metrics.increment(self._drop_field) + return False + + def get(self, block: bool = True, timeout: float | None = None): + return self._queue.get(block=block, timeout=timeout) + + def qsize(self) -> int: + return self._queue.qsize() + + def task_done(self) -> None: + self._queue.task_done() diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/health_server.py b/sample-apps/Ctrl Z/backend/guardian_bridge/health_server.py new file mode 100644 index 000000000..b948371c1 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/health_server.py @@ -0,0 +1,59 @@ +import json +import threading +from http.server import BaseHTTPRequestHandler, HTTPServer + +from config import HEALTH_PORT +from health_metrics import MetricsTracker +from logger_config import setup_logging + +logger = setup_logging(__name__) + + +class _HealthHandler(BaseHTTPRequestHandler): + metrics: MetricsTracker + + def do_GET(self): + if self.path != "/health": + self.send_response(404) + self.end_headers() + return + + body = json.dumps(self.metrics.snapshot()).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, format, *args): + logger.debug("Health server: " + format, *args) + + +class HealthServer: + """Expose bridge pipeline metrics over HTTP.""" + + def __init__(self, metrics: MetricsTracker, port: int = HEALTH_PORT): + self._metrics = metrics + self._port = port + self._thread: threading.Thread | None = None + self._server: HTTPServer | None = None + + def start(self) -> None: + handler = type( + "BridgeHealthHandler", + (_HealthHandler,), + {"metrics": self._metrics}, + ) + self._server = HTTPServer(("0.0.0.0", self._port), handler) + self._thread = threading.Thread( + target=self._server.serve_forever, + name="health-server", + daemon=True, + ) + self._thread.start() + logger.info("Health server listening on http://0.0.0.0:%d/health", self._port) + + def stop(self) -> None: + if self._server: + self._server.shutdown() + self._server = None diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/logger_config.py b/sample-apps/Ctrl Z/backend/guardian_bridge/logger_config.py new file mode 100644 index 000000000..8424e31fd --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/logger_config.py @@ -0,0 +1,62 @@ +import logging +import sys +from logging.handlers import RotatingFileHandler + +from config import ( + DEBUG, + LOG_BACKUP_COUNT, + LOG_FILE, + LOG_LEVEL, + LOG_MAX_BYTES, +) + +_CONSOLE_FORMAT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s" +_FILE_FORMAT = ( + "%(asctime)s [%(levelname)s] %(name)s: %(message)s" + " (%(filename)s:%(lineno)d)" +) + +_resolved_levels: dict[str, int] = {} + + +def _resolve_level(name: str = "guardian_bridge") -> int: + """Resolve the effective log level once per module name.""" + if name not in _resolved_levels: + if LOG_LEVEL in ("DEBUG", "INFO", "WARNING", "WARN", "ERROR", "CRITICAL"): + level = getattr(logging, LOG_LEVEL, logging.INFO) + else: + level = logging.DEBUG if DEBUG else logging.INFO + _resolved_levels[name] = level + return _resolved_levels[name] + + +def setup_logging(name: str = "guardian_bridge") -> logging.Logger: + logger = logging.getLogger(name) + + if logger.handlers: + return logger + + level = _resolve_level(name) + logger.setLevel(level) + logger.propagate = False + + formatter = logging.Formatter(_CONSOLE_FORMAT, datefmt="%Y-%m-%d %H:%M:%S") + + console = logging.StreamHandler(sys.stdout) + console.setLevel(level) + console.setFormatter(formatter) + logger.addHandler(console) + + if LOG_FILE: + file_formatter = logging.Formatter(_FILE_FORMAT, datefmt="%Y-%m-%d %H:%M:%S") + file_handler = RotatingFileHandler( + LOG_FILE, + maxBytes=LOG_MAX_BYTES, + backupCount=LOG_BACKUP_COUNT, + encoding="utf-8", + ) + file_handler.setLevel(level) + file_handler.setFormatter(file_formatter) + logger.addHandler(file_handler) + + return logger diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/main.py b/sample-apps/Ctrl Z/backend/guardian_bridge/main.py new file mode 100644 index 000000000..9e6b19935 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/main.py @@ -0,0 +1,24 @@ +from runtime import GuardianRuntime +from logger_config import setup_logging + +logger = setup_logging(__name__) + + +def main(): + runtime = GuardianRuntime() + runtime.start("../../datasets/raw/test_session.jsonl") + + logger.info("Guardian Bridge running. Press Ctrl+C to stop.") + + try: + while True: + import time + time.sleep(1) + except KeyboardInterrupt: + logger.info("Stopping Guardian Bridge...") + finally: + runtime.stop() + + +if __name__ == "__main__": + main() diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/packet_validator.py b/sample-apps/Ctrl Z/backend/guardian_bridge/packet_validator.py new file mode 100644 index 000000000..e290933ae --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/packet_validator.py @@ -0,0 +1,44 @@ +import re + +from config import DEBUG +from logger_config import setup_logging + +logger = setup_logging(__name__) + +CSI_DATA_PREFIX = "CSI_DATA" +MAX_LINE_LENGTH = 65536 + +# A CSI array opening (optional whitespace, then int or negative int) +CSI_ARRAY_PATTERN = re.compile(r"\[\s*-?\d+") +# Optional trailing quote because the firmware emits the field as "[...]" +BRACKET_BALANCED_PATTERN = re.compile(r"^[^\[]*\[[^\[\]]*\]\"?\s*$") + + +class PacketValidator: + """Validate raw serial lines before parsing.""" + + @staticmethod + def is_valid_ascii(line: str) -> bool: + if not line: + return False + + if len(line) > MAX_LINE_LENGTH: + logger.warning("CSI line exceeds %d bytes, dropping", MAX_LINE_LENGTH) + return False + + if not line.startswith(CSI_DATA_PREFIX): + return False + + if not CSI_ARRAY_PATTERN.search(line): + logger.debug("Missing CSI array in line") + return False + + if not BRACKET_BALANCED_PATTERN.match(line): + logger.debug("Malformed bracket structure in CSI line") + return False + + return True + + @staticmethod + def is_valid(line: str) -> bool: + return PacketValidator.is_valid_ascii(line) diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/port_detector.py b/sample-apps/Ctrl Z/backend/guardian_bridge/port_detector.py new file mode 100644 index 000000000..824b13b76 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/port_detector.py @@ -0,0 +1,54 @@ +import serial +import serial.tools.list_ports + +from config import ESP32_USB_IDS, SERIAL_PORT +from logger_config import setup_logging + +logger = setup_logging(__name__) + + +def scan_esp32_ports() -> list[str]: + """Return all serial ports matching a known ESP32 USB identifier.""" + matches: list[tuple[int, str]] = [] + for port_info in serial.tools.list_ports.comports(): + vid = port_info.vid + pid = port_info.pid + if vid is None or pid is None: + continue + if (vid, pid) in ESP32_USB_IDS: + priority = ESP32_USB_IDS.index((vid, pid)) + matches.append((priority, port_info.device)) + + matches.sort(key=lambda item: item[0]) + return [device for _, device in matches] + + +def find_esp32_port() -> str | None: + """Scan serial ports for known ESP32 USB identifiers (preferred first).""" + devices = scan_esp32_ports() + if not devices: + return None + + logger.info("Found ESP32 device(s): %s", devices) + return devices[0] + + +def resolve_serial_port() -> str: + """Return configured port or auto-detected ESP32 port.""" + if SERIAL_PORT: + logger.info("Using configured serial port: %s", SERIAL_PORT) + return SERIAL_PORT + + detected = find_esp32_port() + if detected: + return detected + + raise serial.SerialException( + "No ESP32 serial port found. Set GUARDIAN_SERIAL_PORT or " + "connect the receiver." + ) + + +def port_available(port: str) -> bool: + """Return True if the given serial port is currently enumerated.""" + return any(p.device == port for p in serial.tools.list_ports.comports()) diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/recorder.py b/sample-apps/Ctrl Z/backend/guardian_bridge/recorder.py new file mode 100644 index 000000000..ea89ca385 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/recorder.py @@ -0,0 +1,80 @@ +from runtime import GuardianRuntime +from pathlib import Path + +ACTIVITIES = { + "1": "walking", + "2": "sitting", + "3": "standing", + "4": "breathing", + "5": "fall", + "6": "empty_room" +} + + +def get_next_filename(activity): + + folder = Path("../../datasets/raw") / activity + + folder.mkdir(parents=True, exist_ok=True) + + existing = list(folder.glob("*.jsonl")) + + filename = f"{activity}_{len(existing)+1:03d}.jsonl" + + return folder / filename + + +def main(): + + print("=" * 50) + print(" GuardianSense Dataset Recorder ") + print("=" * 50) + + print() + + for key, value in ACTIVITIES.items(): + + print(f"{key}. {value.replace('_', ' ').title()}") + + print() + + choice = input("Select Activity: ").strip() + + if choice not in ACTIVITIES: + + print("Invalid choice.") + + return + + activity = ACTIVITIES[choice] + + duration = int(input("Recording Duration (seconds): ")) + + output = get_next_filename(activity) + + runtime = GuardianRuntime() + + runtime.start(str(output)) + + print(f"\nRecording {activity}...\n") + + packets = runtime.record(duration) + + runtime.stop() + + print() + + print("=" * 50) + + print("Recording Complete!") + + print(f"Packets Captured : {packets}") + + print(f"Saved To : {output}") + + print("=" * 50) + + +if __name__ == "__main__": + + main() \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/requirements.txt b/sample-apps/Ctrl Z/backend/guardian_bridge/requirements.txt new file mode 100644 index 000000000..c11d6d77b --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/requirements.txt @@ -0,0 +1,2 @@ +pyserial>=3.5 +requests>=2.31.0 diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/runtime.py b/sample-apps/Ctrl Z/backend/guardian_bridge/runtime.py new file mode 100644 index 000000000..3dc49d5c1 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/runtime.py @@ -0,0 +1,253 @@ +import queue +import threading +import time + +import serial + +from api_client import HttpSender, register_device, start_monitoring +from config import RAW_QUEUE_SIZE, WRITE_FLUSH_INTERVAL +from csi_parser import CSIParser +from dataset_writer import DatasetWriter +from health_metrics import DropAwareQueue, MetricsTracker +from health_server import HealthServer +from logger_config import setup_logging +from packet_validator import PacketValidator +from port_detector import port_available, resolve_serial_port +from serial_manager import SerialManager + +logger = setup_logging(__name__) + +OUTPUT_QUEUE_SIZE = 1000 + + +class GuardianRuntime: + """ + Multi-threaded CSI pipeline: + Serial Reader -> Raw Queue -> Parser -> Output Queue -> Writer + HTTP + """ + + def __init__(self): + self.serial_manager: SerialManager | None = None + self.writer: DatasetWriter | None = None + self.metrics = MetricsTracker() + self._raw_queue = DropAwareQueue( + RAW_QUEUE_SIZE, self.metrics, "raw_queue_drops" + ) + self._output_queue = DropAwareQueue( + OUTPUT_QUEUE_SIZE, self.metrics, "packet_queue_drops" + ) + self._http_sender = HttpSender( + on_error=lambda: self.metrics.increment("http_errors") + ) + self._health_server = HealthServer(self.metrics) + self._running = False + self._threads: list[threading.Thread] = [] + + def start(self, output_file: str) -> None: + if self._running: + return + + while True: + try: + port = resolve_serial_port() + self.serial_manager = SerialManager(port=port) + break + except serial.SerialException as exc: + logger.warning( + "Receiver not found (%s). Plug in ESP32-S3. Retrying in 3s...", + exc, + ) + time.sleep(3) + + register_device() + start_monitoring() + + self.writer = DatasetWriter(output_file) + self._running = True + self._health_server.start() + + self._threads = [ + threading.Thread( + target=self._serial_reader_loop, + name="serial-reader", + daemon=True, + ), + threading.Thread( + target=self._parser_loop, + name="parser", + daemon=True, + ), + threading.Thread( + target=self._output_loop, + name="output", + daemon=True, + ), + ] + for thread in self._threads: + thread.start() + + logger.info("Guardian Runtime started (multi-threaded pipeline)") + + def _reconnect_serial(self) -> None: + try: + if self.serial_manager: + self.serial_manager.close() + self.serial_manager = SerialManager() + self.metrics.increment("serial_reconnects") + logger.info("Serial reconnected on %s", self.serial_manager.port) + except serial.SerialException as exc: + logger.error("Serial reconnect failed: %s", exc) + self.serial_manager = None + + def _serial_reader_loop(self) -> None: + last_port_check = 0.0 + + while self._running: + if not self.serial_manager: + self._reconnect_serial() + time.sleep(1) + continue + + # Detect device unplug/replug while running + now = time.time() + if now - last_port_check >= 5.0: + last_port_check = now + if not port_available(self.serial_manager.port): + logger.warning( + "Serial port %s disappeared, reconnecting...", + self.serial_manager.port, + ) + self.metrics.increment("serial_reconnects") + self.serial_manager.close() + self.serial_manager = None + continue + + try: + lines, binary_packets = self.serial_manager.read_available() + + for packet in binary_packets: + self.metrics.increment("packets_read") + self._raw_queue.put(("binary", packet), timeout=0.01) + + for line in lines: + self.metrics.increment("packets_read") + self._raw_queue.put(("ascii", line), timeout=0.01) + + except serial.SerialException as exc: + logger.error("Serial read error: %s", exc) + self.serial_manager = None + time.sleep(1) + + except Exception as exc: + logger.error("Unexpected serial error: %s", exc, exc_info=True) + time.sleep(0.1) + + def _parser_loop(self) -> None: + while self._running: + try: + kind, data = self._raw_queue.get(timeout=0.5) + except queue.Empty: + continue + + try: + if kind == "binary": + packet = data + else: + line = ( + data.decode("utf-8", errors="ignore").strip() + if isinstance(data, bytes) + else str(data).strip() + ) + if not PacketValidator.is_valid(line): + self.metrics.increment("validation_errors") + continue + + packet = CSIParser.parse(line) + if packet is None: + self.metrics.increment("parse_errors") + continue + + self.metrics.increment("packets_parsed") + self._output_queue.put(packet, timeout=0.01) + + finally: + self._raw_queue.task_done() + + def _output_loop(self) -> None: + """Batch-write to disk and dispatch HTTP sends for each parsed packet.""" + batch: list[dict] = [] + last_flush = time.time() + + while self._running: + try: + packet = self._output_queue.get(timeout=0.5) + batch.append(packet) + self._http_sender.submit(packet) + self.metrics.increment("packets_sent") + self._output_queue.task_done() + except queue.Empty: + pass + + should_flush = batch and ( + len(batch) >= 20 + or (time.time() - last_flush) >= WRITE_FLUSH_INTERVAL + ) + + if should_flush and self.writer: + self.writer.save_batch(batch) + self.metrics.increment("packets_written", len(batch)) + batch = [] + last_flush = time.time() + + if batch and self.writer: + self.writer.save_batch(batch) + + def stop(self) -> None: + self._running = False + self._health_server.stop() + self._http_sender.shutdown(wait=True) + + for thread in self._threads: + thread.join(timeout=2) + + if self.writer: + self.writer.close() + + if self.serial_manager: + self.serial_manager.close() + + logger.info( + "Guardian Runtime stopped. Metrics: %s", self.metrics.snapshot() + ) + + def record(self, duration: float) -> int: + start = time.time() + initial = self.metrics.snapshot()["packets_parsed"] + while time.time() - start < duration: + time.sleep(0.1) + final = self.metrics.snapshot()["packets_parsed"] + return final - initial + + def process_packet(self): + """Legacy single-packet API for tests.""" + if not self.serial_manager: + return None + + lines, binary_packets = self.serial_manager.read_available() + for packet in binary_packets: + if self.writer: + self.writer.save(packet) + self._http_sender.submit(packet) + return packet + + for line in lines: + text = line.decode("utf-8", errors="ignore").strip() + if not PacketValidator.is_valid(text): + continue + packet = CSIParser.parse(text) + if packet: + if self.writer: + self.writer.save(packet) + self._http_sender.submit(packet) + return packet + return None diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/serial_manager.py b/sample-apps/Ctrl Z/backend/guardian_bridge/serial_manager.py new file mode 100644 index 000000000..2a20dc521 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/serial_manager.py @@ -0,0 +1,86 @@ +import serial + +from binary_parser import BinaryCSIParser +from config import BAUD_RATE +from logger_config import setup_logging +from port_detector import resolve_serial_port + +logger = setup_logging(__name__) + + +class SerialManager: + """Serial reader with line reassembly and binary frame support.""" + + def __init__(self, port: str | None = None): + self._port_name = port or resolve_serial_port() + self._serial: serial.Serial | None = None + self._line_buffer = bytearray() + self._binary_parser = BinaryCSIParser() + self._connect() + + def _connect(self) -> None: + if self._serial and self._serial.is_open: + self._serial.close() + + self._serial = serial.Serial( + self._port_name, + BAUD_RATE, + timeout=0.05, + ) + self._line_buffer.clear() + self._binary_parser = BinaryCSIParser() + logger.info("Serial connected on %s @ %d baud", self._port_name, BAUD_RATE) + + @property + def port(self) -> str: + return self._port_name + + def reconnect(self, port: str | None = None) -> None: + if port: + self._port_name = port + else: + self._port_name = resolve_serial_port() + self._connect() + + def read_available(self) -> tuple[list[bytes], list[dict]]: + """ + Read all available serial data. + Returns (ascii_lines, binary_packets). + """ + if not self._serial or not self._serial.is_open: + return [], [] + + waiting = self._serial.in_waiting + chunk = self._serial.read(max(waiting, 1)) + if not chunk: + return [], [] + + binary_packets = self._binary_parser.feed(chunk) + + self._line_buffer.extend(chunk) + lines: list[bytes] = [] + + while True: + newline_idx = self._line_buffer.find(b"\n") + if newline_idx == -1: + break + raw_line = bytes(self._line_buffer[: newline_idx + 1]) + del self._line_buffer[: newline_idx + 1] + line = raw_line.strip() + if line: + lines.append(line) + + # Prevent unbounded buffer growth on malformed streams + if len(self._line_buffer) > 65536: + logger.warning( + "Line buffer overflow (%d bytes), clearing", + len(self._line_buffer), + ) + self._line_buffer.clear() + + return lines, binary_packets + + def close(self) -> None: + if self._serial and self._serial.is_open: + self._serial.close() + logger.info("Serial port closed") diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/serial_test.py b/sample-apps/Ctrl Z/backend/guardian_bridge/serial_test.py new file mode 100644 index 000000000..87555f3f6 --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/serial_test.py @@ -0,0 +1,13 @@ +import serial + +ser = serial.Serial("COM5", 921600, timeout=1) + +print("Connected!") + +while True: + try: + line = ser.readline() + print(line) + except Exception as e: + print(e) + break \ No newline at end of file diff --git a/sample-apps/Ctrl Z/backend/guardian_bridge/test_bridge.py b/sample-apps/Ctrl Z/backend/guardian_bridge/test_bridge.py new file mode 100644 index 000000000..9bf2b596d --- /dev/null +++ b/sample-apps/Ctrl Z/backend/guardian_bridge/test_bridge.py @@ -0,0 +1,201 @@ +"""Unit tests for the Guardian Bridge parsing and validation pipeline.""" + +import os +import struct +import sys +import tempfile +import unittest +from unittest import mock + +os.environ.setdefault("GUARDIAN_LOG_FILE", os.path.join(tempfile.gettempdir(), "guardian_test.log")) +os.environ.setdefault("GUARDIAN_BACKEND_URL", "http://localhost:1/api") + +from api_client import _request_with_retry +from binary_parser import BinaryCSIParser, HEADER_FMT, HEADER_SIZE, MAGIC +from csi_parser import CSIParser +from health_metrics import DropAwareQueue, MetricsTracker +from packet_validator import PacketValidator + +S3_MAC = "1a:00:00:00:00:00" + +S3_LINE = ( + 'CSI_DATA,0,1a:00:00:00:00:00,-26,7,1,7,1,1,0,1,0,0,1,-90,0,11,0,' + '12345,0,3,0,3,0,"[10,-20,30]"' +) + +C5C6_LINE = ( + 'CSI_DATA,0,1a:00:00:00:00:00,-26,7,-90,1,2,11,12345,3,0,3,0,"[10,-20,30]"' +) + + +class CsiParserTest(unittest.TestCase): + def test_parses_s3_schema(self): + packet = CSIParser.parse(S3_LINE) + self.assertIsNotNone(packet) + self.assertEqual(packet["packet_type"], "CSI_DATA") + self.assertEqual(packet["mac"], S3_MAC) + self.assertEqual(packet["rssi"], -26) + self.assertEqual(packet["channel"], 11) + self.assertEqual(packet["schema"], "s3") + self.assertEqual(packet["csi"], [10, -20, 30]) + self.assertEqual(packet["mcs"], 7) + self.assertEqual(packet["sig_mode"], 1) + self.assertEqual(packet["secondary_channel"], 0) + self.assertEqual(packet["len"], 3) + + def test_parses_c5c6_schema(self): + packet = CSIParser.parse(C5C6_LINE) + self.assertIsNotNone(packet) + self.assertEqual(packet["schema"], "c5c6") + self.assertEqual(packet["agc_gain"], 2) + self.assertEqual(packet["fft_gain"], 1) + self.assertEqual(packet["csi"], [10, -20, 30]) + + def test_parses_bytes_input(self): + self.assertIsNotNone(CSIParser.parse(S3_LINE.encode("utf-8"))) + + def test_rejects_non_csi_lines(self): + self.assertIsNone(CSIParser.parse("hello world")) + self.assertIsNone(CSIParser.parse("")) + + def test_rejects_wrong_field_count(self): + line = S3_LINE + ",extra" + self.assertIsNone(CSIParser.parse(line)) + + def test_rejects_invalid_mac(self): + line = S3_LINE.replace(S3_MAC, "zz:00:00:00:00:00") + self.assertIsNone(CSIParser.parse(line)) + + def test_rejects_csi_length_mismatch(self): + # Change the header `len` field (fields[-3]) from 3 to 9 + line = S3_LINE.replace('3,0,"[10,-20,30]"', '9,0,"[10,-20,30]"') + self.assertIsNone(CSIParser.parse(line)) + + def test_rejects_empty_array(self): + line = S3_LINE.replace("[10,-20,30]", "[]") + self.assertIsNone(CSIParser.parse(line)) + + def test_rejects_non_integer_csi_values(self): + line = S3_LINE.replace("[10,-20,30]", "[10,-20,x]") + self.assertIsNone(CSIParser.parse(line)) + + def test_rejects_out_of_range_rssi(self): + line = S3_LINE.replace(",-26,", ",50,") + self.assertIsNone(CSIParser.parse(line)) + + +class PacketValidatorTest(unittest.TestCase): + def test_accepts_valid_line(self): + self.assertTrue(PacketValidator.is_valid(S3_LINE)) + + def test_rejects_missing_prefix(self): + self.assertFalse(PacketValidator.is_valid("DATA,0,1,")) + + def test_rejects_unbalanced_brackets(self): + line = S3_LINE.replace("]", "] extra [") + self.assertFalse(PacketValidator.is_valid(line)) + + def test_rejects_empty(self): + self.assertFalse(PacketValidator.is_valid("")) + + def test_rejects_oversized_line(self): + self.assertFalse(PacketValidator.is_valid("CSI_DATA," + "x" * 100000)) + + +def _build_binary_frame(csi, checksum_override=None): + payload = struct.pack( + HEADER_FMT, + MAGIC, + 1, + 42, + -30, + 11, + -90, + 3, + 2, + len(csi), + 123456, + ) + payload += struct.pack(f"<{len(csi)}h", *csi) + checksum = checksum_override + if checksum is None: + checksum = sum(payload) & 0xFFFF + payload += struct.pack(" +#include +#include + +#include "nvs_flash.h" + +#include "esp_mac.h" +#include "rom/ets_sys.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_netif.h" +#include "esp_now.h" +#include "esp_csi_gain_ctrl.h" + +#define CONFIG_LESS_INTERFERENCE_CHANNEL 11 +#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 || (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) +#define CONFIG_WIFI_BAND_MODE WIFI_BAND_MODE_2G_ONLY +#define CONFIG_WIFI_2G_BANDWIDTHS WIFI_BW_HT40 +#define CONFIG_WIFI_5G_BANDWIDTHS WIFI_BW_HT40 +#define CONFIG_WIFI_2G_PROTOCOL WIFI_PROTOCOL_11N +#define CONFIG_WIFI_5G_PROTOCOL WIFI_PROTOCOL_11N +#else +#define CONFIG_WIFI_BANDWIDTH WIFI_BW_HT40 +#endif + +#define CONFIG_ESP_NOW_PHYMODE WIFI_PHY_MODE_HT40 +#define CONFIG_ESP_NOW_RATE WIFI_PHY_RATE_MCS0_LGI +#define CONFIG_FORCE_GAIN 0 + +#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 +#define CSI_FORCE_LLTF 0 +#endif + +#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 +#define CONFIG_GAIN_CONTROL 1 +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#define ESP_IF_WIFI_STA ESP_MAC_WIFI_STA +#endif + +static const uint8_t CONFIG_CSI_SEND_MAC[] = {0x1a, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const char *TAG = "csi_recv"; + +static void wifi_init() +{ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_netif_init()); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + +#if CONFIG_IDF_TARGET_ESP32C5 + ESP_ERROR_CHECK(esp_wifi_start()); + esp_wifi_set_band_mode(CONFIG_WIFI_BAND_MODE); + wifi_protocols_t protocols = { + .ghz_2g = CONFIG_WIFI_2G_PROTOCOL, + .ghz_5g = CONFIG_WIFI_5G_PROTOCOL + }; + ESP_ERROR_CHECK(esp_wifi_set_protocols(ESP_IF_WIFI_STA, &protocols)); + wifi_bandwidths_t bandwidth = { + .ghz_2g = CONFIG_WIFI_2G_BANDWIDTHS, + .ghz_5g = CONFIG_WIFI_5G_BANDWIDTHS + }; + ESP_ERROR_CHECK(esp_wifi_set_bandwidths(ESP_IF_WIFI_STA, &bandwidth)); +#elif (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) || CONFIG_IDF_TARGET_ESP32C61 + ESP_ERROR_CHECK(esp_wifi_start()); + esp_wifi_set_band_mode(CONFIG_WIFI_BAND_MODE); + wifi_protocols_t protocols = { + .ghz_2g = CONFIG_WIFI_2G_PROTOCOL, + }; + ESP_ERROR_CHECK(esp_wifi_set_protocols(ESP_IF_WIFI_STA, &protocols)); + wifi_bandwidths_t bandwidth = { + .ghz_2g = CONFIG_WIFI_2G_BANDWIDTHS, + }; + ESP_ERROR_CHECK(esp_wifi_set_bandwidths(ESP_IF_WIFI_STA, &bandwidth)); +#else + ESP_ERROR_CHECK(esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, CONFIG_WIFI_BANDWIDTH)); + ESP_ERROR_CHECK(esp_wifi_start()); +#endif + + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); +#if CONFIG_IDF_TARGET_ESP32C5 + if ((CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_2G_ONLY && CONFIG_WIFI_2G_BANDWIDTHS == WIFI_BW_HT20) + || (CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_5G_ONLY && CONFIG_WIFI_5G_BANDWIDTHS == WIFI_BW_HT20)) { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE)); + } else { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW)); + } +#elif (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) || CONFIG_IDF_TARGET_ESP32C61 + if (CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_2G_ONLY && CONFIG_WIFI_2G_BANDWIDTHS == WIFI_BW_HT20) { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE)); + } else { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW)); + } +#else + if (CONFIG_WIFI_BANDWIDTH == WIFI_BW_HT20) { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE)); + } else { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW)); + } +#endif + + ESP_ERROR_CHECK(esp_wifi_set_mac(WIFI_IF_STA, CONFIG_CSI_SEND_MAC)); +} + +static void wifi_esp_now_init(esp_now_peer_info_t peer) +{ + ESP_ERROR_CHECK(esp_now_init()); + ESP_ERROR_CHECK(esp_now_set_pmk((uint8_t *)"pmk1234567890123")); + esp_now_rate_config_t rate_config = { + .phymode = CONFIG_ESP_NOW_PHYMODE, + .rate = CONFIG_ESP_NOW_RATE,// WIFI_PHY_RATE_MCS0_LGI, + .ersu = false, + .dcm = false + }; + ESP_ERROR_CHECK(esp_now_add_peer(&peer)); + ESP_ERROR_CHECK(esp_now_set_peer_rate_config(peer.peer_addr, &rate_config)); + +} + +static void wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info) +{ + if (!info || !info->buf) { + ESP_LOGW(TAG, "<%s> wifi_csi_cb", esp_err_to_name(ESP_ERR_INVALID_ARG)); + return; + } + + if (memcmp(info->mac, CONFIG_CSI_SEND_MAC, 6)) { + return; + } + + const wifi_pkt_rx_ctrl_t *rx_ctrl = &info->rx_ctrl; + static int s_count = 0; + float compensate_gain = 1.0f; + static uint8_t agc_gain = 0; + static int8_t fft_gain = 0; +#if CONFIG_GAIN_CONTROL + static uint8_t agc_gain_baseline = 0; + static int8_t fft_gain_baseline = 0; + esp_csi_gain_ctrl_get_rx_gain(rx_ctrl, &agc_gain, &fft_gain); + if (s_count < 100) { + esp_csi_gain_ctrl_record_rx_gain(agc_gain, fft_gain); + } else if (s_count == 100) { + esp_csi_gain_ctrl_get_rx_gain_baseline(&agc_gain_baseline, &fft_gain_baseline); +#if CONFIG_FORCE_GAIN + esp_csi_gain_ctrl_set_rx_force_gain(agc_gain_baseline, fft_gain_baseline); + ESP_LOGD(TAG, "fft_force %d, agc_force %d", fft_gain_baseline, agc_gain_baseline); +#endif + } + esp_csi_gain_ctrl_get_gain_compensation(&compensate_gain, agc_gain, fft_gain); + ESP_LOGI(TAG, "compensate_gain %f, agc_gain %d, fft_gain %d", compensate_gain, agc_gain, fft_gain); +#endif + + uint32_t rx_id = *(uint32_t *)(info->payload + 15); +#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 + if (!s_count) { + ESP_LOGI(TAG, "================ CSI RECV ================"); + ets_printf("type,seq,mac,rssi,rate,noise_floor,fft_gain,agc_gain,channel,local_timestamp,sig_len,rx_format,len,first_word,data\n"); + } + + ets_printf("CSI_DATA,%d," MACSTR ",%d,%d,%d,%d,%d,%d,%d,%d,%d", + rx_id, MAC2STR(info->mac), rx_ctrl->rssi, rx_ctrl->rate, + rx_ctrl->noise_floor, fft_gain, agc_gain, rx_ctrl->channel, + rx_ctrl->timestamp, rx_ctrl->sig_len, rx_ctrl->cur_bb_format); +#else + if (!s_count) { + ESP_LOGI(TAG, "================ CSI RECV ================"); + ets_printf("type,id,mac,rssi,rate,sig_mode,mcs,bandwidth,smoothing,not_sounding,aggregation,stbc,fec_coding,sgi,noise_floor,ampdu_cnt,channel,secondary_channel,local_timestamp,ant,sig_len,rx_format,len,first_word,data\n"); + } + + ets_printf("CSI_DATA,%d," MACSTR ",%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + rx_id, MAC2STR(info->mac), rx_ctrl->rssi, rx_ctrl->rate, rx_ctrl->sig_mode, + rx_ctrl->mcs, rx_ctrl->cwb, rx_ctrl->smoothing, rx_ctrl->not_sounding, + rx_ctrl->aggregation, rx_ctrl->stbc, rx_ctrl->fec_coding, rx_ctrl->sgi, + rx_ctrl->noise_floor, rx_ctrl->ampdu_cnt, rx_ctrl->channel, rx_ctrl->secondary_channel, + rx_ctrl->timestamp, rx_ctrl->ant, rx_ctrl->sig_len, rx_ctrl->sig_mode); + +#endif +#if (CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61) && CSI_FORCE_LLTF + int16_t csi = ((int16_t)(((((uint16_t)info->buf[1]) << 8) | info->buf[0]) << 4) >> 4); + ets_printf(",%d,%d,\"[%d", (info->len - 2) / 2, info->first_word_invalid, (int16_t)(compensate_gain * csi)); + for (int i = 2; i < (info->len - 2); i += 2) { + csi = ((int16_t)(((((uint16_t)info->buf[i + 1]) << 8) | info->buf[i]) << 4) >> 4); + ets_printf(",%d", (int16_t)(compensate_gain * csi)); + } +#else + ets_printf(",%d,%d,\"[%d", info->len, info->first_word_invalid, (int16_t)(compensate_gain * info->buf[0])); + for (int i = 1; i < info->len; i++) { + ets_printf(",%d", (int16_t)(compensate_gain * info->buf[i])); + } +#endif + ets_printf("]\"\n"); + s_count++; +} + +static void wifi_csi_init() +{ + ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); + + /**< default config */ +#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 + wifi_csi_config_t csi_config = { + .enable = true, + .acquire_csi_legacy = false, + .acquire_csi_force_lltf = CSI_FORCE_LLTF, + .acquire_csi_ht20 = true, + .acquire_csi_ht40 = true, + .acquire_csi_vht = false, + .acquire_csi_su = false, + .acquire_csi_mu = false, + .acquire_csi_dcm = false, + .acquire_csi_beamformed = false, + .acquire_csi_he_stbc_mode = 2, + .val_scale_cfg = 0, + .dump_ack_en = false, + .reserved = false + }; +#elif CONFIG_IDF_TARGET_ESP32C6 + wifi_csi_config_t csi_config = { + .enable = true, + .acquire_csi_legacy = false, + .acquire_csi_ht20 = true, + .acquire_csi_ht40 = true, + .acquire_csi_su = true, + .acquire_csi_mu = true, + .acquire_csi_dcm = true, + .acquire_csi_beamformed = true, + .acquire_csi_he_stbc = 2, + .val_scale_cfg = false, + .dump_ack_en = false, + .reserved = false + }; +#else + wifi_csi_config_t csi_config = { + .lltf_en = true, + .htltf_en = true, + .stbc_htltf2_en = true, + .ltf_merge_en = true, + .channel_filter_en = true, + .manu_scale = false, + .shift = false, + }; +#endif + ESP_ERROR_CHECK(esp_wifi_set_csi_config(&csi_config)); + ESP_ERROR_CHECK(esp_wifi_set_csi_rx_cb(wifi_csi_rx_cb, NULL)); + ESP_ERROR_CHECK(esp_wifi_set_csi(true)); +} + +void app_main() +{ + /** + * @brief Initialize NVS + */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + /** + * @brief Initialize Wi-Fi + */ + wifi_init(); + + /** + * @brief Initialize ESP-NOW + * ESP-NOW protocol see: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html + */ + + esp_now_peer_info_t peer = { + .channel = CONFIG_LESS_INTERFERENCE_CHANNEL, + .ifidx = WIFI_IF_STA, + .encrypt = false, + .peer_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }; + + wifi_esp_now_init(peer); + + wifi_csi_init(); +} diff --git a/sample-apps/Ctrl Z/firmware/esp32-s3-receiver/main/idf_component.yml b/sample-apps/Ctrl Z/firmware/esp32-s3-receiver/main/idf_component.yml new file mode 100644 index 000000000..7f4789bf5 --- /dev/null +++ b/sample-apps/Ctrl Z/firmware/esp32-s3-receiver/main/idf_component.yml @@ -0,0 +1,4 @@ +## IDF Component Manager Manifest File +dependencies: + idf: ">=4.4.1" + esp_csi_gain_ctrl: ">=0.1.4" diff --git a/sample-apps/Ctrl Z/firmware/esp32-s3-receiver/sdkconfig.defaults b/sample-apps/Ctrl Z/firmware/esp32-s3-receiver/sdkconfig.defaults new file mode 100644 index 000000000..c4e654d1d --- /dev/null +++ b/sample-apps/Ctrl Z/firmware/esp32-s3-receiver/sdkconfig.defaults @@ -0,0 +1,10 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration +# +CONFIG_PARTITION_TABLE_OFFSET=0xa000 +CONFIG_ESP_CONSOLE_UART_CUSTOM=y +CONFIG_ESP_CONSOLE_UART_BAUDRATE=921600 +CONFIG_ESP_TASK_WDT_TIMEOUT_S=30 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=128 +CONFIG_ESP_WIFI_CSI_ENABLED=y +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n diff --git a/sample-apps/Ctrl Z/firmware/esp32-sender/CMakeLists.txt b/sample-apps/Ctrl Z/firmware/esp32-sender/CMakeLists.txt new file mode 100644 index 000000000..c9c57fd03 --- /dev/null +++ b/sample-apps/Ctrl Z/firmware/esp32-sender/CMakeLists.txt @@ -0,0 +1,13 @@ + +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctlycmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.5) +add_compile_options(-fdiagnostics-color=always) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +string(REGEX REPLACE ".*/\(.*\)" "\\1" CURDIR ${CMAKE_CURRENT_SOURCE_DIR}) +project(${CURDIR}) + +git_describe(PROJECT_VERSION ${COMPONENT_DIR}) +message("Project commit: " ${PROJECT_VERSION}) diff --git a/sample-apps/Ctrl Z/firmware/esp32-sender/README.md b/sample-apps/Ctrl Z/firmware/esp32-sender/README.md new file mode 100644 index 000000000..d22214e52 --- /dev/null +++ b/sample-apps/Ctrl Z/firmware/esp32-sender/README.md @@ -0,0 +1 @@ +# CSI_SEND diff --git a/sample-apps/Ctrl Z/firmware/esp32-sender/main/CMakeLists.txt b/sample-apps/Ctrl Z/firmware/esp32-sender/main/CMakeLists.txt new file mode 100644 index 000000000..a941e22ba --- /dev/null +++ b/sample-apps/Ctrl Z/firmware/esp32-sender/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS ".") diff --git a/sample-apps/Ctrl Z/firmware/esp32-sender/main/app_main.c b/sample-apps/Ctrl Z/firmware/esp32-sender/main/app_main.c new file mode 100644 index 000000000..e26ee5bcb --- /dev/null +++ b/sample-apps/Ctrl Z/firmware/esp32-sender/main/app_main.c @@ -0,0 +1,170 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/* Get Start Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include +#include + +#include "nvs_flash.h" + +#include "esp_mac.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_netif.h" +#include "esp_now.h" + +#define CONFIG_LESS_INTERFERENCE_CHANNEL 11 + +#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 || (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) +#define CONFIG_WIFI_BAND_MODE WIFI_BAND_MODE_2G_ONLY +#define CONFIG_WIFI_2G_BANDWIDTHS WIFI_BW_HT40 +#define CONFIG_WIFI_5G_BANDWIDTHS WIFI_BW_HT40 +#define CONFIG_WIFI_2G_PROTOCOL WIFI_PROTOCOL_11N +#define CONFIG_WIFI_5G_PROTOCOL WIFI_PROTOCOL_11N +#else +#define CONFIG_WIFI_BANDWIDTH WIFI_BW_HT40 +#endif + +#define CONFIG_ESP_NOW_PHYMODE WIFI_PHY_MODE_HT40 +#define CONFIG_ESP_NOW_RATE WIFI_PHY_RATE_MCS0_LGI +#define CONFIG_SEND_FREQUENCY 100 + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#define ESP_IF_WIFI_STA ESP_MAC_WIFI_STA +#endif + +static const uint8_t CONFIG_CSI_SEND_MAC[] = {0x1a, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const char *TAG = "csi_send"; + +static void wifi_init() +{ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + ESP_ERROR_CHECK(esp_netif_init()); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + +#if CONFIG_IDF_TARGET_ESP32C5 + ESP_ERROR_CHECK(esp_wifi_start()); + esp_wifi_set_band_mode(CONFIG_WIFI_BAND_MODE); + wifi_protocols_t protocols = { + .ghz_2g = CONFIG_WIFI_2G_PROTOCOL, + .ghz_5g = CONFIG_WIFI_5G_PROTOCOL + }; + ESP_ERROR_CHECK(esp_wifi_set_protocols(ESP_IF_WIFI_STA, &protocols)); + wifi_bandwidths_t bandwidth = { + .ghz_2g = CONFIG_WIFI_2G_BANDWIDTHS, + .ghz_5g = CONFIG_WIFI_5G_BANDWIDTHS + }; + ESP_ERROR_CHECK(esp_wifi_set_bandwidths(ESP_IF_WIFI_STA, &bandwidth)); +#elif (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) || CONFIG_IDF_TARGET_ESP32C61 + ESP_ERROR_CHECK(esp_wifi_start()); + esp_wifi_set_band_mode(CONFIG_WIFI_BAND_MODE); + wifi_protocols_t protocols = { + .ghz_2g = CONFIG_WIFI_2G_PROTOCOL, + }; + ESP_ERROR_CHECK(esp_wifi_set_protocols(ESP_IF_WIFI_STA, &protocols)); + wifi_bandwidths_t bandwidth = { + .ghz_2g = CONFIG_WIFI_2G_BANDWIDTHS, + }; + ESP_ERROR_CHECK(esp_wifi_set_bandwidths(ESP_IF_WIFI_STA, &bandwidth)); +#else + ESP_ERROR_CHECK(esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, CONFIG_WIFI_BANDWIDTH)); + ESP_ERROR_CHECK(esp_wifi_start()); + +#endif + + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); +#if CONFIG_IDF_TARGET_ESP32C5 + if ((CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_2G_ONLY && CONFIG_WIFI_2G_BANDWIDTHS == WIFI_BW_HT20) + || (CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_5G_ONLY && CONFIG_WIFI_5G_BANDWIDTHS == WIFI_BW_HT20)) { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE)); + } else { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW)); + } +#elif (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) || CONFIG_IDF_TARGET_ESP32C61 + if (CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_2G_ONLY && CONFIG_WIFI_2G_BANDWIDTHS == WIFI_BW_HT20) { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE)); + } else { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW)); + } +#else + if (CONFIG_WIFI_BANDWIDTH == WIFI_BW_HT20) { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE)); + } else { + ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW)); + } +#endif + ESP_ERROR_CHECK(esp_wifi_set_mac(WIFI_IF_STA, CONFIG_CSI_SEND_MAC)); +} + +static void wifi_esp_now_init(esp_now_peer_info_t peer) +{ + ESP_ERROR_CHECK(esp_now_init()); + ESP_ERROR_CHECK(esp_now_set_pmk((uint8_t *)"pmk1234567890123")); + ESP_ERROR_CHECK(esp_now_add_peer(&peer)); + esp_now_rate_config_t rate_config = { + .phymode = CONFIG_ESP_NOW_PHYMODE, + .rate = CONFIG_ESP_NOW_RATE, + .ersu = false, + .dcm = false + }; + ESP_ERROR_CHECK(esp_now_set_peer_rate_config(peer.peer_addr, &rate_config)); +} + +void app_main() +{ + /** + * @brief Initialize NVS + */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + /** + * @brief Initialize Wi-Fi + */ + wifi_init(); + + /** + * @brief Initialize ESP-NOW + * ESP-NOW protocol see: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html + */ + esp_now_peer_info_t peer = { + .channel = CONFIG_LESS_INTERFERENCE_CHANNEL, + .ifidx = WIFI_IF_STA, + .encrypt = false, + .peer_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }; + wifi_esp_now_init(peer); + + ESP_LOGI(TAG, "================ CSI SEND ================"); + ESP_LOGI(TAG, "wifi_channel: %d, send_frequency: %d, mac: " MACSTR, + CONFIG_LESS_INTERFERENCE_CHANNEL, CONFIG_SEND_FREQUENCY, MAC2STR(CONFIG_CSI_SEND_MAC)); + + for (uint32_t count = 0; ; ++count) { + esp_err_t ret = esp_now_send(peer.peer_addr, (const uint8_t *)&count, sizeof(count)); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "free_heap: %ld <%s> ESP-NOW send error", esp_get_free_heap_size(), esp_err_to_name(ret)); + } + + usleep(1000 * 1000 / CONFIG_SEND_FREQUENCY); + } +} diff --git a/sample-apps/Ctrl Z/firmware/esp32-sender/main/idf_component.yml b/sample-apps/Ctrl Z/firmware/esp32-sender/main/idf_component.yml new file mode 100644 index 000000000..c7558914f --- /dev/null +++ b/sample-apps/Ctrl Z/firmware/esp32-sender/main/idf_component.yml @@ -0,0 +1,3 @@ +## IDF Component Manager Manifest File +dependencies: + idf: ">=4.4.1" diff --git a/sample-apps/Ctrl Z/firmware/esp32-sender/sdkconfig.defaults b/sample-apps/Ctrl Z/firmware/esp32-sender/sdkconfig.defaults new file mode 100644 index 000000000..5b90ed927 --- /dev/null +++ b/sample-apps/Ctrl Z/firmware/esp32-sender/sdkconfig.defaults @@ -0,0 +1,8 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration +# +CONFIG_PARTITION_TABLE_OFFSET=0xa000 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=128 +CONFIG_ESP_WIFI_CSI_ENABLED=y +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n +CONFIG_FREERTOS_HZ=1000 diff --git a/sample-apps/Ctrl Z/frontend/.gitignore b/sample-apps/Ctrl Z/frontend/.gitignore new file mode 100644 index 000000000..5ef6a5207 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/sample-apps/Ctrl Z/frontend/AGENTS.md b/sample-apps/Ctrl Z/frontend/AGENTS.md new file mode 100644 index 000000000..8bd0e3908 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/AGENTS.md @@ -0,0 +1,5 @@ + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + diff --git a/sample-apps/Ctrl Z/frontend/CLAUDE.md b/sample-apps/Ctrl Z/frontend/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/sample-apps/Ctrl Z/frontend/README.md b/sample-apps/Ctrl Z/frontend/README.md new file mode 100644 index 000000000..e215bc4cc --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/sample-apps/Ctrl Z/frontend/eslint.config.mjs b/sample-apps/Ctrl Z/frontend/eslint.config.mjs new file mode 100644 index 000000000..05e726d1b --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/sample-apps/Ctrl Z/frontend/next.config.ts b/sample-apps/Ctrl Z/frontend/next.config.ts new file mode 100644 index 000000000..fa67b356d --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/next.config.ts @@ -0,0 +1,9 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + turbopack: { + root: __dirname, + }, +}; + +export default nextConfig; diff --git a/sample-apps/Ctrl Z/frontend/package-lock.json b/sample-apps/Ctrl Z/frontend/package-lock.json new file mode 100644 index 000000000..766ce69f3 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/package-lock.json @@ -0,0 +1,7337 @@ +{ + "name": "frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.1.0", + "dependencies": { + "axios": "^1.19.0", + "lucide-react": "^1.28.0", + "next": "16.2.12", + "react": "19.2.4", + "react-dom": "19.2.4", + "recharts": "^3.10.1" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.12", + "tailwindcss": "^4", + "typescript": "^5" + } + }, + "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/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "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/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@next/env": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.12.tgz", + "integrity": "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.12.tgz", + "integrity": "sha512-uF2z/qAK2q7B5/6CpnFcBRX6jOq5iCO+Uqh1UkJhXljX1JwLarLYhhoJadO6dPb6moTprOKewMXheBcbIoSbug==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.12.tgz", + "integrity": "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.12.tgz", + "integrity": "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.12.tgz", + "integrity": "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.12.tgz", + "integrity": "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.12.tgz", + "integrity": "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.12.tgz", + "integrity": "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.12.tgz", + "integrity": "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.12.tgz", + "integrity": "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "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/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/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/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "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/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "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/axe-core": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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/baseline-browser-mapping": { + "version": "2.11.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.9.tgz", + "integrity": "sha512-cp447VUsGS07+n1Dqf7YSQ8maeJrjEhaDxTm1ZefbqDtypHBC5GzGMQbklR6IPR13Y8OAJRHZWEMtZipJLCttg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "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": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "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==", + "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/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "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": "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/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/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/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/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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/electron-to-chromium": { + "version": "1.5.399", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.399.tgz", + "integrity": "sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==", + "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.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "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-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.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", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "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==", + "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-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-toolkit": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types" + ] + }, + "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.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.12.tgz", + "integrity": "sha512-iaaf4vvKo5h2LBdGt0JuRv7t0Ysqr9FMCiFxbptDg8LqOE//mIKR80DdpOnSVM7qjLH3jT8P0aFiwXxBEGZRXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.2.12", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/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/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "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.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "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.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/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/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/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/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": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "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/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "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/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "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==", + "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/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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/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/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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-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==", + "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==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "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/immer": { + "version": "11.1.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.15.tgz", + "integrity": "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/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/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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-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-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-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/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.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "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-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "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-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "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-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-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-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-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-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": "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/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/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "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.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/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-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "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/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.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/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/lucide-react": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.28.0.tgz", + "integrity": "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "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/next": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.12.tgz", + "integrity": "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.12", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.12", + "@next/swc-darwin-x64": "16.2.12", + "@next/swc-linux-arm64-gnu": "16.2.12", + "@next/swc-linux-arm64-musl": "16.2.12", + "@next/swc-linux-x64-gnu": "16.2.12", + "@next/swc-linux-x64-musl": "16.2.12", + "@next/swc-win32-arm64-msvc": "16.2.12", + "@next/swc-win32-x64-msvc": "16.2.12", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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-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/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "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/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "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": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/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.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "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.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/recharts": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^11.1.8", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "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/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "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": "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/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "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/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.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "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/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/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "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/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "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.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "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-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-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/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "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/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": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "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/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "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/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.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/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/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "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-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.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "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/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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/sample-apps/Ctrl Z/frontend/package.json b/sample-apps/Ctrl Z/frontend/package.json new file mode 100644 index 000000000..a5556fb88 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/package.json @@ -0,0 +1,29 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "axios": "^1.19.0", + "lucide-react": "^1.28.0", + "next": "16.2.12", + "react": "19.2.4", + "react-dom": "19.2.4", + "recharts": "^3.10.1" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.12", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/sample-apps/Ctrl Z/frontend/postcss.config.mjs b/sample-apps/Ctrl Z/frontend/postcss.config.mjs new file mode 100644 index 000000000..61e36849c --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/sample-apps/Ctrl Z/frontend/public/file.svg b/sample-apps/Ctrl Z/frontend/public/file.svg new file mode 100644 index 000000000..004145cdd --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sample-apps/Ctrl Z/frontend/public/globe.svg b/sample-apps/Ctrl Z/frontend/public/globe.svg new file mode 100644 index 000000000..567f17b0d --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sample-apps/Ctrl Z/frontend/public/next.svg b/sample-apps/Ctrl Z/frontend/public/next.svg new file mode 100644 index 000000000..5174b28c5 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sample-apps/Ctrl Z/frontend/public/vercel.svg b/sample-apps/Ctrl Z/frontend/public/vercel.svg new file mode 100644 index 000000000..770539603 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sample-apps/Ctrl Z/frontend/public/window.svg b/sample-apps/Ctrl Z/frontend/public/window.svg new file mode 100644 index 000000000..b2b2a44f6 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sample-apps/Ctrl Z/frontend/src/app/devices/page.tsx b/sample-apps/Ctrl Z/frontend/src/app/devices/page.tsx new file mode 100644 index 000000000..46824ee9c --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/src/app/devices/page.tsx @@ -0,0 +1,30 @@ +import type { Metadata } from "next"; +import Sidebar from "@/components/layout/Sidebar"; +import Navbar from "@/components/layout/Navbar"; +import DeviceList from "@/components/device/DeviceList"; + +export const metadata: Metadata = { + title: "Devices", +}; + +export default function DevicesPage() { + return ( +
+ + +
+ + +
+

+ Devices +

+

+ Connected ESP32 receivers +

+ +
+
+
+ ); +} diff --git a/sample-apps/Ctrl Z/frontend/src/app/favicon.ico b/sample-apps/Ctrl Z/frontend/src/app/favicon.ico new file mode 100644 index 000000000..718d6fea4 Binary files /dev/null and b/sample-apps/Ctrl Z/frontend/src/app/favicon.ico differ diff --git a/sample-apps/Ctrl Z/frontend/src/app/globals.css b/sample-apps/Ctrl Z/frontend/src/app/globals.css new file mode 100644 index 000000000..c2a5a07d6 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/src/app/globals.css @@ -0,0 +1,128 @@ +@import "tailwindcss"; + +@custom-variant dark (&:where(.dark, .dark *)); + +:root { + --background: #f8fafc; + --foreground: #0f172a; + --card: #ffffff; + --card-foreground: #0f172a; + --muted: #f1f5f9; + --muted-foreground: #64748b; + --border: #e2e8f0; + --input: #e2e8f0; + --primary: #0891b2; + --primary-foreground: #ffffff; + --accent: #e0f2fe; + --accent-foreground: #155e75; + --success: #059669; + --success-foreground: #ecfdf5; + --warning: #d97706; + --warning-foreground: #fffbeb; + --destructive: #dc2626; + --destructive-foreground: #fef2f2; + --ring: #06b6d4; + --chart-grid: #e2e8f0; + --chart-tick: #94a3b8; + --tooltip-bg: #ffffff; + --tooltip-border: #e2e8f0; + --sidebar: #ffffff; + --sidebar-foreground: #0f172a; + --sidebar-muted: #64748b; + --sidebar-border: #e2e8f0; + --sidebar-active: #0891b2; +} + +.dark { + --background: #0b1220; + --foreground: #f1f5f9; + --card: #0f172a; + --card-foreground: #f1f5f9; + --muted: #1e293b; + --muted-foreground: #94a3b8; + --border: #1e293b; + --input: #334155; + --primary: #06b6d4; + --primary-foreground: #083344; + --accent: #164e63; + --accent-foreground: #a5f3fc; + --success: #10b981; + --success-foreground: #022c22; + --warning: #f59e0b; + --warning-foreground: #451a03; + --destructive: #ef4444; + --destructive-foreground: #450a0a; + --ring: #22d3ee; + --chart-grid: #1e293b; + --chart-tick: #64748b; + --tooltip-bg: #1e293b; + --tooltip-border: #334155; + --sidebar: #0f172a; + --sidebar-foreground: #f1f5f9; + --sidebar-muted: #94a3b8; + --sidebar-border: #1e293b; + --sidebar-active: #06b6d4; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); + --color-warning: var(--warning); + --color-warning-foreground: var(--warning-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-ring: var(--ring); + --color-chart-grid: var(--chart-grid); + --color-chart-tick: var(--chart-tick); + --color-tooltip-bg: var(--tooltip-bg); + --color-tooltip-border: var(--tooltip-border); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-muted: var(--sidebar-muted); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-active: var(--sidebar-active); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +html { + color-scheme: light; +} + +html.dark { + color-scheme: dark; +} + +body { + background: var(--background); + color: var(--foreground); + font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + transition: background-color 200ms ease, color 200ms ease; +} + +.tabular-nums { + font-variant-numeric: tabular-nums; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} diff --git a/sample-apps/Ctrl Z/frontend/src/app/guardian-ai/page.tsx b/sample-apps/Ctrl Z/frontend/src/app/guardian-ai/page.tsx new file mode 100644 index 000000000..a70f19548 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/src/app/guardian-ai/page.tsx @@ -0,0 +1,196 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + Brain, + CheckCircle2, + HeartPulse, + Lightbulb, + ShieldAlert, +} from "lucide-react"; + +import api from "@/services/api"; +import Sidebar from "@/components/layout/Sidebar"; +import Navbar from "@/components/layout/Navbar"; +import { useWebSocket } from "@/hooks/useWebSocket"; + +interface LiveData { + respiration: number; + motion: string; + confidence: number; + risk: string; +} + +function getAIExplanation(live: LiveData) { + if (live.risk === "High") { + return { + title: "Potential health concern detected", + reasoning: + "Respiration is outside the expected range. Guardian AI recommends immediate attention and closer observation.", + recommendation: + "Notify caregiver and continue continuous monitoring.", + }; + } + + if (live.motion === "Walking") { + return { + title: "Normal walking activity detected", + reasoning: + "CSI signal energy indicates sustained body movement while respiration remains stable.", + recommendation: + "Continue monitoring. No intervention is currently required.", + }; + } + + return { + title: "Patient appears stationary", + reasoning: + "Minimal body movement detected with respiration inside the expected healthy range.", + recommendation: + "Continue passive monitoring. No abnormal behavior detected.", + }; +} + +export default function GuardianAIPage() { + const [live, setLive] = useState({ + respiration: 0, + motion: "Waiting…", + confidence: 0, + risk: "Unknown", + }); + + const { connected } = useWebSocket({ + event: "LIVE_UPDATE", + onMessage: (message) => { + setLive({ + respiration: message.respiration, + motion: message.motion, + confidence: message.confidence, + risk: message.risk, + }); + }, + }); + + useEffect(() => { + api + .get("/live") + .then((res) => setLive(res.data)) + .catch(() => undefined); + }, []); + + const ai = getAIExplanation(live); + + return ( +
+ + +
+ + +
+

+ Guardian AI +

+

+ Real-time AI health assessment +

+ +
+
+ +
+
+

+

+
+ {[ + { + label: "Respiration", + value: `${live.respiration} bpm`, + icon: HeartPulse, + }, + { label: "Motion", value: live.motion, icon: Brain }, + { + label: "Confidence", + value: `${live.confidence}%`, + icon: CheckCircle2, + }, + { + label: "Risk", + value: live.risk, + icon: ShieldAlert, + tone: + live.risk === "High" + ? "text-destructive" + : live.risk === "Medium" + ? "text-warning" + : "text-success", + }, + ].map(({ label, value, icon: Icon, tone }) => ( +
+ + +
+

{label}

+

+ {value} +

+
+
+ ))} +
+
+ +
+

+

+ +
+
+

+ Summary +

+

+ {ai.title} +

+
+ +
+

+ Reasoning +

+

+ {ai.reasoning} +

+
+ +
+

+ Recommendation +

+

+ {ai.recommendation} +

+
+
+
+
+
+
+
+ ); +} diff --git a/sample-apps/Ctrl Z/frontend/src/app/layout.tsx b/sample-apps/Ctrl Z/frontend/src/app/layout.tsx new file mode 100644 index 000000000..caa3cc258 --- /dev/null +++ b/sample-apps/Ctrl Z/frontend/src/app/layout.tsx @@ -0,0 +1,53 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: { + default: "GuardianSense — Contactless Health Monitoring", + template: "%s · GuardianSense", + }, + description: + "Real-time, contactless health monitoring powered by WiFi CSI and Guardian AI. Track respiration, movement, and risk assessment from a live dashboard.", + applicationName: "GuardianSense", +}; + +const themeInitScript = `(function () { + try { + var stored = window.localStorage.getItem("guardiansense-theme"); + var prefersLight = window.matchMedia("(prefers-color-scheme: light)").matches; + var theme = stored === "light" || stored === "dark" + ? stored + : prefersLight ? "light" : "dark"; + document.documentElement.classList.toggle("dark", theme === "dark"); + } catch (e) {} +})();`; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + +