From cc37e12448d5947466252400af188df56f2cb2e0 Mon Sep 17 00:00:00 2001 From: Sahil-Patel180 Date: Sat, 1 Aug 2026 13:39:29 +0530 Subject: [PATCH 1/2] Update README.md --- sample-apps/README.md | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/sample-apps/README.md b/sample-apps/README.md index 9a695fde4..9679d94cf 100644 --- a/sample-apps/README.md +++ b/sample-apps/README.md @@ -1,19 +1,29 @@ -# CrisisMesh-server +# How to submit your project — step by step -CrisisMesh is a multilingual, human-supervised emergency coordination MCP server. It helps convert voice or text emergency reports into structured SOS incidents, identifies nearby safe shelters, shares consented live location, and routes cases to a simulated dispatcher dashboard for human review. This prototype uses synthetic emergency data and does not contact real emergency services. +No special permissions needed. If you can use GitHub, you can do this: -## Getting Started +1. **Fork the repo** — open [github.com/nitrocloudofficial/nitrostack](https://github.com/nitrocloudofficial/nitrostack) and click **Fork** (top right). -```bash -npm install -npm run dev -``` +2. **Clone your fork** — on your machine: -## Building for Production + ```bash + git clone https://github.com/YOUR_USERNAME/nitrostack.git + ``` -```bash -npm run build -npm start -``` +3. **Create a branch** — e.g. -Built with [NitroStack](https://nitrostack.ai) ⚡ + ```bash + git checkout -b add-sample-Team-Nova + ``` + +4. **Add your project** — place your hackathon app in the `sample-apps/` folder (include a README with what it does, how to run it, and your team name). Clean up any secrets or API keys first. + +5. **Commit & push**: + + ```bash + git add . + git commit -m "Add Team Nova MCP sample app" + git push origin add-sample-Team-Nova + ``` + +6. **Open a Pull Request** — go to your fork on GitHub, click **Compare & pull request**, describe your project in a few sentences, and submit. We'll review and merge eligible contributions. From 2d612e67709e0a9e39ddf9c6add84d11c68c2692 Mon Sep 17 00:00:00 2001 From: Sahil-Patel180 Date: Sat, 1 Aug 2026 13:50:47 +0530 Subject: [PATCH 2/2] SwiftCare - AI emergency escalation MCP server --- .../.agents/skills/auth-security/SKILL.md | 141 + .../skills/mcp-app-architecture/SKILL.md | 176 + .../skills/middleware-pipeline/SKILL.md | 235 + .../skills/tools-resources-prompts/SKILL.md | 287 + .../Triage/.agents/skills/ui-widgets/SKILL.md | 251 + .../skills/auth-security/SKILL.md | 141 + .../skills/mcp-app-architecture/SKILL.md | 176 + .../skills/middleware-pipeline/SKILL.md | 235 + .../skills/tools-resources-prompts/SKILL.md | 287 + .../.antigravity/skills/ui-widgets/SKILL.md | 251 + .../.claude/skills/auth-security/SKILL.md | 141 + .../skills/mcp-app-architecture/SKILL.md | 176 + .../skills/middleware-pipeline/SKILL.md | 235 + .../skills/tools-resources-prompts/SKILL.md | 287 + .../Triage/.claude/skills/ui-widgets/SKILL.md | 251 + .../.codex/skills/auth-security/SKILL.md | 141 + .../skills/mcp-app-architecture/SKILL.md | 176 + .../skills/middleware-pipeline/SKILL.md | 235 + .../skills/tools-resources-prompts/SKILL.md | 287 + .../Triage/.codex/skills/ui-widgets/SKILL.md | 251 + .../.copilot/skills/auth-security/SKILL.md | 141 + .../skills/mcp-app-architecture/SKILL.md | 176 + .../skills/middleware-pipeline/SKILL.md | 235 + .../skills/tools-resources-prompts/SKILL.md | 287 + .../.copilot/skills/ui-widgets/SKILL.md | 251 + .../.cursor/skills/auth-security/SKILL.md | 141 + .../skills/mcp-app-architecture/SKILL.md | 176 + .../skills/middleware-pipeline/SKILL.md | 235 + .../skills/tools-resources-prompts/SKILL.md | 287 + .../Triage/.cursor/skills/ui-widgets/SKILL.md | 251 + sample-apps/Triage/.env.example | 22 + .../.gemini/skills/auth-security/SKILL.md | 141 + .../skills/mcp-app-architecture/SKILL.md | 176 + .../skills/middleware-pipeline/SKILL.md | 235 + .../skills/tools-resources-prompts/SKILL.md | 287 + .../Triage/.gemini/skills/ui-widgets/SKILL.md | 251 + sample-apps/Triage/.gitignore | 57 + .../.opencode/skills/auth-security/SKILL.md | 141 + .../skills/mcp-app-architecture/SKILL.md | 176 + .../skills/middleware-pipeline/SKILL.md | 235 + .../skills/tools-resources-prompts/SKILL.md | 287 + .../.opencode/skills/ui-widgets/SKILL.md | 251 + sample-apps/Triage/LICENSE | 21 + sample-apps/Triage/README.md | 54 + sample-apps/Triage/package-lock.json | 4800 +++++++++++++++++ sample-apps/Triage/package.json | 42 + sample-apps/Triage/src/app.module.ts | 34 + sample-apps/Triage/src/index.ts | 30 + .../modules/hospital-finder/hospital.data.ts | 43 + .../hospital-finder/hospital.module.ts | 12 + .../hospital-finder/hospital.service.ts | 41 + .../modules/hospital-finder/hospital.tools.ts | 33 + .../notification/notification.module.ts | 12 + .../notification/notification.service.ts | 24 + .../notification/notification.tools.ts | 45 + .../orchestrator/orchestrator.module.ts | 10 + .../orchestrator/orchestrator.tools.ts | 83 + .../Triage/src/modules/pizzaz/pizzaz.data.ts | 106 + .../src/modules/pizzaz/pizzaz.module.ts | 12 + .../src/modules/pizzaz/pizzaz.service.ts | 60 + .../Triage/src/modules/pizzaz/pizzaz.tasks.ts | 294 + .../Triage/src/modules/pizzaz/pizzaz.tools.ts | 212 + .../Triage/src/modules/triage/triage.data.ts | 21 + .../src/modules/triage/triage.module.ts | 13 + .../src/modules/triage/triage.service.ts | 62 + .../Triage/src/modules/triage/triage.tasks.ts | 11 + .../Triage/src/modules/triage/triage.tools.ts | 42 + sample-apps/Triage/src/widgets/app/layout.tsx | 18 + .../src/widgets/app/pizza-list/page.tsx | 272 + .../Triage/src/widgets/app/pizza-map/page.tsx | 216 + .../src/widgets/app/pizza-shop/page.tsx | 374 ++ .../widgets/components/CompactShopCard.tsx | 144 + .../src/widgets/components/PizzaCard.tsx | 191 + sample-apps/Triage/src/widgets/next-env.d.ts | 5 + sample-apps/Triage/src/widgets/next.config.js | 45 + .../Triage/src/widgets/package-lock.json | 3697 +++++++++++++ sample-apps/Triage/src/widgets/package.json | 32 + sample-apps/Triage/src/widgets/tsconfig.json | 28 + .../Triage/src/widgets/widget-manifest.json | 253 + sample-apps/Triage/tsconfig.json | 23 + 80 files changed, 20214 insertions(+) create mode 100644 sample-apps/Triage/.agents/skills/auth-security/SKILL.md create mode 100644 sample-apps/Triage/.agents/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/Triage/.agents/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/Triage/.agents/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/Triage/.agents/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/Triage/.antigravity/skills/auth-security/SKILL.md create mode 100644 sample-apps/Triage/.antigravity/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/Triage/.antigravity/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/Triage/.antigravity/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/Triage/.antigravity/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/Triage/.claude/skills/auth-security/SKILL.md create mode 100644 sample-apps/Triage/.claude/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/Triage/.claude/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/Triage/.claude/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/Triage/.claude/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/Triage/.codex/skills/auth-security/SKILL.md create mode 100644 sample-apps/Triage/.codex/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/Triage/.codex/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/Triage/.codex/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/Triage/.codex/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/Triage/.copilot/skills/auth-security/SKILL.md create mode 100644 sample-apps/Triage/.copilot/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/Triage/.copilot/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/Triage/.copilot/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/Triage/.copilot/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/Triage/.cursor/skills/auth-security/SKILL.md create mode 100644 sample-apps/Triage/.cursor/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/Triage/.cursor/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/Triage/.cursor/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/Triage/.cursor/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/Triage/.env.example create mode 100644 sample-apps/Triage/.gemini/skills/auth-security/SKILL.md create mode 100644 sample-apps/Triage/.gemini/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/Triage/.gemini/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/Triage/.gemini/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/Triage/.gemini/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/Triage/.gitignore create mode 100644 sample-apps/Triage/.opencode/skills/auth-security/SKILL.md create mode 100644 sample-apps/Triage/.opencode/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/Triage/.opencode/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/Triage/.opencode/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/Triage/.opencode/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/Triage/LICENSE create mode 100644 sample-apps/Triage/README.md create mode 100644 sample-apps/Triage/package-lock.json create mode 100644 sample-apps/Triage/package.json create mode 100644 sample-apps/Triage/src/app.module.ts create mode 100644 sample-apps/Triage/src/index.ts create mode 100644 sample-apps/Triage/src/modules/hospital-finder/hospital.data.ts create mode 100644 sample-apps/Triage/src/modules/hospital-finder/hospital.module.ts create mode 100644 sample-apps/Triage/src/modules/hospital-finder/hospital.service.ts create mode 100644 sample-apps/Triage/src/modules/hospital-finder/hospital.tools.ts create mode 100644 sample-apps/Triage/src/modules/notification/notification.module.ts create mode 100644 sample-apps/Triage/src/modules/notification/notification.service.ts create mode 100644 sample-apps/Triage/src/modules/notification/notification.tools.ts create mode 100644 sample-apps/Triage/src/modules/orchestrator/orchestrator.module.ts create mode 100644 sample-apps/Triage/src/modules/orchestrator/orchestrator.tools.ts create mode 100644 sample-apps/Triage/src/modules/pizzaz/pizzaz.data.ts create mode 100644 sample-apps/Triage/src/modules/pizzaz/pizzaz.module.ts create mode 100644 sample-apps/Triage/src/modules/pizzaz/pizzaz.service.ts create mode 100644 sample-apps/Triage/src/modules/pizzaz/pizzaz.tasks.ts create mode 100644 sample-apps/Triage/src/modules/pizzaz/pizzaz.tools.ts create mode 100644 sample-apps/Triage/src/modules/triage/triage.data.ts create mode 100644 sample-apps/Triage/src/modules/triage/triage.module.ts create mode 100644 sample-apps/Triage/src/modules/triage/triage.service.ts create mode 100644 sample-apps/Triage/src/modules/triage/triage.tasks.ts create mode 100644 sample-apps/Triage/src/modules/triage/triage.tools.ts create mode 100644 sample-apps/Triage/src/widgets/app/layout.tsx create mode 100644 sample-apps/Triage/src/widgets/app/pizza-list/page.tsx create mode 100644 sample-apps/Triage/src/widgets/app/pizza-map/page.tsx create mode 100644 sample-apps/Triage/src/widgets/app/pizza-shop/page.tsx create mode 100644 sample-apps/Triage/src/widgets/components/CompactShopCard.tsx create mode 100644 sample-apps/Triage/src/widgets/components/PizzaCard.tsx create mode 100644 sample-apps/Triage/src/widgets/next-env.d.ts create mode 100644 sample-apps/Triage/src/widgets/next.config.js create mode 100644 sample-apps/Triage/src/widgets/package-lock.json create mode 100644 sample-apps/Triage/src/widgets/package.json create mode 100644 sample-apps/Triage/src/widgets/tsconfig.json create mode 100644 sample-apps/Triage/src/widgets/widget-manifest.json create mode 100644 sample-apps/Triage/tsconfig.json diff --git a/sample-apps/Triage/.agents/skills/auth-security/SKILL.md b/sample-apps/Triage/.agents/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.agents/skills/mcp-app-architecture/SKILL.md b/sample-apps/Triage/.agents/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.agents/skills/middleware-pipeline/SKILL.md b/sample-apps/Triage/.agents/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.agents/skills/tools-resources-prompts/SKILL.md b/sample-apps/Triage/.agents/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.agents/skills/ui-widgets/SKILL.md b/sample-apps/Triage/.agents/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.antigravity/skills/auth-security/SKILL.md b/sample-apps/Triage/.antigravity/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.antigravity/skills/mcp-app-architecture/SKILL.md b/sample-apps/Triage/.antigravity/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.antigravity/skills/middleware-pipeline/SKILL.md b/sample-apps/Triage/.antigravity/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.antigravity/skills/tools-resources-prompts/SKILL.md b/sample-apps/Triage/.antigravity/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.antigravity/skills/ui-widgets/SKILL.md b/sample-apps/Triage/.antigravity/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.claude/skills/auth-security/SKILL.md b/sample-apps/Triage/.claude/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.claude/skills/mcp-app-architecture/SKILL.md b/sample-apps/Triage/.claude/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.claude/skills/middleware-pipeline/SKILL.md b/sample-apps/Triage/.claude/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.claude/skills/tools-resources-prompts/SKILL.md b/sample-apps/Triage/.claude/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.claude/skills/ui-widgets/SKILL.md b/sample-apps/Triage/.claude/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.codex/skills/auth-security/SKILL.md b/sample-apps/Triage/.codex/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.codex/skills/mcp-app-architecture/SKILL.md b/sample-apps/Triage/.codex/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.codex/skills/middleware-pipeline/SKILL.md b/sample-apps/Triage/.codex/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.codex/skills/tools-resources-prompts/SKILL.md b/sample-apps/Triage/.codex/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.codex/skills/ui-widgets/SKILL.md b/sample-apps/Triage/.codex/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.copilot/skills/auth-security/SKILL.md b/sample-apps/Triage/.copilot/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.copilot/skills/mcp-app-architecture/SKILL.md b/sample-apps/Triage/.copilot/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.copilot/skills/middleware-pipeline/SKILL.md b/sample-apps/Triage/.copilot/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.copilot/skills/tools-resources-prompts/SKILL.md b/sample-apps/Triage/.copilot/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.copilot/skills/ui-widgets/SKILL.md b/sample-apps/Triage/.copilot/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.cursor/skills/auth-security/SKILL.md b/sample-apps/Triage/.cursor/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.cursor/skills/mcp-app-architecture/SKILL.md b/sample-apps/Triage/.cursor/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.cursor/skills/middleware-pipeline/SKILL.md b/sample-apps/Triage/.cursor/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.cursor/skills/tools-resources-prompts/SKILL.md b/sample-apps/Triage/.cursor/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.cursor/skills/ui-widgets/SKILL.md b/sample-apps/Triage/.cursor/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.env.example b/sample-apps/Triage/.env.example new file mode 100644 index 000000000..0c3f65fa5 --- /dev/null +++ b/sample-apps/Triage/.env.example @@ -0,0 +1,22 @@ +# 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 + +# Mapbox Configuration (Optional) +# ============================================================================= +# The map widget uses Mapbox GL for interactive maps. +# Get a free API key at: https://www.mapbox.com/ +# ============================================================================= + +# Your Mapbox Public Token (starts with pk.) +NEXT_PUBLIC_MAPBOX_TOKEN=pk.your_mapbox_token_here diff --git a/sample-apps/Triage/.gemini/skills/auth-security/SKILL.md b/sample-apps/Triage/.gemini/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.gemini/skills/mcp-app-architecture/SKILL.md b/sample-apps/Triage/.gemini/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.gemini/skills/middleware-pipeline/SKILL.md b/sample-apps/Triage/.gemini/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.gemini/skills/tools-resources-prompts/SKILL.md b/sample-apps/Triage/.gemini/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.gemini/skills/ui-widgets/SKILL.md b/sample-apps/Triage/.gemini/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.gitignore b/sample-apps/Triage/.gitignore new file mode 100644 index 000000000..b5b5532bc --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.opencode/skills/auth-security/SKILL.md b/sample-apps/Triage/.opencode/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.opencode/skills/mcp-app-architecture/SKILL.md b/sample-apps/Triage/.opencode/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.opencode/skills/middleware-pipeline/SKILL.md b/sample-apps/Triage/.opencode/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.opencode/skills/tools-resources-prompts/SKILL.md b/sample-apps/Triage/.opencode/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/Triage/.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/Triage/.opencode/skills/ui-widgets/SKILL.md b/sample-apps/Triage/.opencode/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/Triage/.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/Triage/LICENSE b/sample-apps/Triage/LICENSE new file mode 100644 index 000000000..7334a6bb1 --- /dev/null +++ b/sample-apps/Triage/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Sahil Patel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sample-apps/Triage/README.md b/sample-apps/Triage/README.md new file mode 100644 index 000000000..1a533fbd4 --- /dev/null +++ b/sample-apps/Triage/README.md @@ -0,0 +1,54 @@ +# NitroStack Pizzaz Template + +Template focused on rich, interactive widget experiences (map/list/detail flows) +using the NitroStack widget SDK patterns. + +## What This Template Includes + +- Widget-heavy module and UI structure +- Interactive examples for advanced frontends +- Optional map provider integration pattern +- Studio-friendly development workflow + +## Quick Start + +```bash +npx @nitrostack/cli init my-pizzaz-app --template typescript-pizzaz +cd my-pizzaz-app +npm run dev +``` + +## Optional Configuration + +If this project uses a map provider, configure API tokens in widget `.env` files +as documented in the template source. + +## Common Commands + +```bash +npm run dev +npm run build +npm start +npm run widget +``` + +## NitroStudio + +NitroStudio is the fastest way to test and debug interactive widget output. + +- Download: +- Studio: + +## Links + +- Docs: +- Widgets docs: +- Main repository: + +## Community + +- Discord: +- X: +- YouTube: +- LinkedIn: +- GitHub: diff --git a/sample-apps/Triage/package-lock.json b/sample-apps/Triage/package-lock.json new file mode 100644 index 000000000..f70420737 --- /dev/null +++ b/sample-apps/Triage/package-lock.json @@ -0,0 +1,4800 @@ +{ + "name": "my-mcp-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "my-mcp-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "@nitrostack/core": "^1", + "dotenv": "^16.3.1", + "nodemailer": "^9.0.3", + "twilio": "^6.0.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "@nitrostack/cli": "^1", + "@types/node": "^22.10.0", + "@types/nodemailer": "^8.0.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/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/nodemailer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.1.tgz", + "integrity": "sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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/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/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": "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/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/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/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/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/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/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "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/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/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/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/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/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/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/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/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/form-data/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/form-data/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/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/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/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/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/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/nodemailer": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.3.tgz", + "integrity": "sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.47.2.tgz", + "integrity": "sha512-u62sg9bAE+GO8NUzBnE0+PMgelTOoCfvFwcMvUK/zxvZLO8A25j3zbjXxJo2iBC3Cs6zzuJXENsFtsf58pk+tg==", + "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/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/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/scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", + "deprecated": "Just use Node.js's crypto.timingSafeEqual()", + "license": "BSD-3-Clause" + }, + "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/twilio": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-6.0.2.tgz", + "integrity": "sha512-RN3TZxUtxLz2HBZVt62+LdZxQbrMVgYKtuzLgwmO7nqKvR+gQS5mCackD9hf4Y7MmoK/bX7tCm7kaJC8kC8zFA==", + "license": "MIT", + "dependencies": { + "axios": "^1.13.5", + "dayjs": "^1.11.9", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^9.0.3", + "qs": "^6.14.1", + "scmp": "^2.1.0", + "xmlbuilder": "^13.0.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "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/xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "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/Triage/package.json b/sample-apps/Triage/package.json new file mode 100644 index 000000000..6647007d6 --- /dev/null +++ b/sample-apps/Triage/package.json @@ -0,0 +1,42 @@ +{ + "name": "my-mcp-server", + "version": "1.0.0", + "private": true, + "type": "module", + "description": "My awesome MCP server", + "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" + }, + "keywords": [ + "nitrostack", + "mcp", + "pizza", + "map", + "widgets" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "@nitrostack/core": "^1", + "dotenv": "^16.3.1", + "nodemailer": "^9.0.3", + "twilio": "^6.0.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "@nitrostack/cli": "^1", + "@types/node": "^22.10.0", + "@types/nodemailer": "^8.0.1", + "typescript": "^5.3.3" + }, + "nitrostack": { + "skillsVersion": "1.0.0" + } +} diff --git a/sample-apps/Triage/src/app.module.ts b/sample-apps/Triage/src/app.module.ts new file mode 100644 index 000000000..e681a92e2 --- /dev/null +++ b/sample-apps/Triage/src/app.module.ts @@ -0,0 +1,34 @@ +import { McpApp, Module, ConfigModule } from '@nitrostack/core'; +import { PizzazModule } from './modules/pizzaz/pizzaz.module.js'; +import { TriageModule } from './modules/triage/triage.module.js'; +import { HospitalModule } from './modules/hospital-finder/hospital.module.js'; +import { NotificationModule } from './modules/notification/notification.module.js'; +import { OrchestratorModule } from './modules/orchestrator/orchestrator.module.js'; +/** + * Root Application Module + * + * Pizza shop finder with interactive maps. + * Showcases NitroStack Widget SDK features. + */ +@McpApp({ + module: AppModule, + server: { + name: 'pizzaz-finder', + version: '1.0.0' + }, + logging: { + level: 'info' + } +}) +@Module({ + name: 'app', + imports: [ + ConfigModule.forRoot(), + PizzazModule, + TriageModule, + HospitalModule, + NotificationModule, + OrchestratorModule + ], +}) +export class AppModule { } \ No newline at end of file diff --git a/sample-apps/Triage/src/index.ts b/sample-apps/Triage/src/index.ts new file mode 100644 index 000000000..4a343b505 --- /dev/null +++ b/sample-apps/Triage/src/index.ts @@ -0,0 +1,30 @@ +/** + * Pizzaz MCP Server + * + * Pizza shop finder with interactive map widgets. + * Showcases NitroStack Widget SDK features including: + * - useTheme() for dark mode + * - useWidgetState() for persistent favorites + * - useDisplayMode() for fullscreen support + * - useMaxHeight() for responsive layouts + * - useWidgetSDK() for tool calling and navigation + */ + +import 'dotenv/config'; +import { McpApplicationFactory } from '@nitrostack/core'; +import { AppModule } from './app.module.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/Triage/src/modules/hospital-finder/hospital.data.ts b/sample-apps/Triage/src/modules/hospital-finder/hospital.data.ts new file mode 100644 index 000000000..20b0f7e5a --- /dev/null +++ b/sample-apps/Triage/src/modules/hospital-finder/hospital.data.ts @@ -0,0 +1,43 @@ +// src/modules/hospital-finder/hospital.data.ts + +export interface Hospital { + id: string; + name: string; + address: string; + lat: number; + lng: number; + specialties: string[]; + phone: string; + emergencyReady: boolean; +} + +// sample seed — swap/expand with real rows from Hospitals-in-India dataset +export const HOSPITALS: Hospital[] = [ + { + id: 'sgh-vadodara', + name: 'Sunshine General Hospital', + address: 'Alkapuri, Vadodara, Gujarat', + lat: 22.3072, lng: 73.1812, + specialties: ['cardiology', 'emergency', 'trauma'], + phone: '0265-1234567', + emergencyReady: true + }, + { + id: 'city-care-vadodara', + name: 'City Care Multispeciality Hospital', + address: 'Fatehgunj, Vadodara, Gujarat', + lat: 22.3181, lng: 73.1929, + specialties: ['general', 'pediatrics', 'orthopedics'], + phone: '0265-2345678', + emergencyReady: true + }, + { + id: 'apex-vadodara', + name: 'Apex Multispeciality Hospital', + address: 'Gotri, Vadodara, Gujarat', + lat: 22.2891, lng: 73.1547, + specialties: ['emergency', 'neurology', 'general'], + phone: '0265-3456789', + emergencyReady: false + } +]; \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/hospital-finder/hospital.module.ts b/sample-apps/Triage/src/modules/hospital-finder/hospital.module.ts new file mode 100644 index 000000000..3b750e4e1 --- /dev/null +++ b/sample-apps/Triage/src/modules/hospital-finder/hospital.module.ts @@ -0,0 +1,12 @@ +// src/modules/hospital-finder/hospital.module.ts +import { Module } from '@nitrostack/core'; +import { HospitalService } from './hospital.service.js'; +import { HospitalTools } from './hospital.tools.js'; + +@Module({ + name: 'hospital-finder', + description: 'Finds nearest suitable hospital by location and specialty', + controllers: [HospitalTools], + providers: [HospitalService], +}) +export class HospitalModule {} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/hospital-finder/hospital.service.ts b/sample-apps/Triage/src/modules/hospital-finder/hospital.service.ts new file mode 100644 index 000000000..7d86fa847 --- /dev/null +++ b/sample-apps/Triage/src/modules/hospital-finder/hospital.service.ts @@ -0,0 +1,41 @@ +// src/modules/hospital-finder/hospital.service.ts +import { Injectable } from '@nitrostack/core'; +import { HOSPITALS, Hospital } from './hospital.data.js'; + +function haversineKm(lat1: number, lng1: number, lat2: number, lng2: number): number { + const R = 6371; + const dLat = (lat2 - lat1) * Math.PI / 180; + const dLng = (lng2 - lng1) * Math.PI / 180; + const a = + Math.sin(dLat / 2) ** 2 + + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * + Math.sin(dLng / 2) ** 2; + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); +} + +@Injectable() +export class HospitalService { + + findNearest(lat: number, lng: number, requireEmergency = true, specialty?: string) { + let candidates: Hospital[] = HOSPITALS; + + if (requireEmergency) { + candidates = candidates.filter(h => h.emergencyReady); + } + if (specialty) { + candidates = candidates.filter(h => + h.specialties.some(s => s.toLowerCase().includes(specialty.toLowerCase())) + ); + } + // fallback: if specialty filter empties the list, drop it rather than return nothing + if (candidates.length === 0 && specialty) { + candidates = requireEmergency ? HOSPITALS.filter(h => h.emergencyReady) : HOSPITALS; + } + + const ranked = candidates + .map(h => ({ ...h, distanceKm: Math.round(haversineKm(lat, lng, h.lat, h.lng) * 10) / 10 })) + .sort((a, b) => a.distanceKm - b.distanceKm); + + return ranked; + } +} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/hospital-finder/hospital.tools.ts b/sample-apps/Triage/src/modules/hospital-finder/hospital.tools.ts new file mode 100644 index 000000000..79fee27fa --- /dev/null +++ b/sample-apps/Triage/src/modules/hospital-finder/hospital.tools.ts @@ -0,0 +1,33 @@ +// src/modules/hospital-finder/hospital.tools.ts +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { HospitalService } from './hospital.service.js'; + +const FindHospitalSchema = z.object({ + lat: z.number().describe('Patient current latitude'), + lng: z.number().describe('Patient current longitude'), + specialty: z.string().optional().describe('Preferred specialty, e.g. cardiology'), + emergencyOnly: z.boolean().optional().default(true) +}); + +@Injectable({ deps: [HospitalService] }) +export class HospitalTools { + constructor(private readonly hospitalService: HospitalService) {} + + @Tool({ + name: 'find_nearest_hospital', + description: 'Find nearest suitable hospital given patient coordinates, optional specialty and emergency-readiness filter. Returns ranked list, nearest first.', + inputSchema: FindHospitalSchema + }) + async findNearestHospital(args: z.infer, ctx: ExecutionContext) { + const results = this.hospitalService.findNearest( + args.lat, args.lng, args.emergencyOnly, args.specialty + ); + + ctx.logger.info('Hospital search', { lat: args.lat, lng: args.lng, found: results.length }); + + return { + count: results.length, + hospitals: results.slice(0, 5) // top 5 + }; + } +} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/notification/notification.module.ts b/sample-apps/Triage/src/modules/notification/notification.module.ts new file mode 100644 index 000000000..5fea4c889 --- /dev/null +++ b/sample-apps/Triage/src/modules/notification/notification.module.ts @@ -0,0 +1,12 @@ +// src/modules/notification/notification.module.ts +import { Module } from '@nitrostack/core'; +import { NotificationService } from './notification.service.js'; +import { NotificationTools } from './notification.tools.js'; + +@Module({ + name: 'notification', + description: 'Sends emergency alert SMS to contacts', + controllers: [NotificationTools], + providers: [NotificationService], +}) +export class NotificationModule {} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/notification/notification.service.ts b/sample-apps/Triage/src/modules/notification/notification.service.ts new file mode 100644 index 000000000..79112dd6a --- /dev/null +++ b/sample-apps/Triage/src/modules/notification/notification.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nitrostack/core'; +import nodemailer from 'nodemailer'; + +@Injectable() +export class NotificationService { + private transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.GMAIL_USER, + pass: process.env.GMAIL_APP_PASSWORD + } + }); + + async sendAlert(toEmail: string, subject: string, message: string) { + const result = await this.transporter.sendMail({ + from: process.env.GMAIL_USER, + to: toEmail, + subject, + text: message + }); + + return { messageId: result.messageId, accepted: result.accepted }; + } +} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/notification/notification.tools.ts b/sample-apps/Triage/src/modules/notification/notification.tools.ts new file mode 100644 index 000000000..88c37fc93 --- /dev/null +++ b/sample-apps/Triage/src/modules/notification/notification.tools.ts @@ -0,0 +1,45 @@ +// src/modules/notification/notification.tools.ts +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { NotificationService } from './notification.service.js'; + +const SendAlertSchema = z.object({ + contactEmail: z.string().email(), + patientName: z.string(), + condition: z.string(), + hospitalName: z.string().optional(), + liveLocationUrl: z.string().optional() +}); + +@Injectable({ deps: [NotificationService] }) +export class NotificationTools { + constructor(private readonly notificationService: NotificationService) {} + + @Tool({ + name: 'send_emergency_alert', + description: 'Draft and send emergency alert SMS to a contact with patient condition, hospital, and live location.', + inputSchema: SendAlertSchema + }) + async sendEmergencyAlert(args: z.infer, ctx: ExecutionContext) { + const message = + `🚨 EMERGENCY ALERT\n` + + `${args.patientName} needs urgent help.\n` + + `Condition: ${args.condition}\n` + + (args.hospitalName ? `Nearest hospital: ${args.hospitalName}\n` : '') + + (args.liveLocationUrl ? `Location: ${args.liveLocationUrl}\n` : '') + + `Please respond immediately.`; + + ctx.logger.info('Sending emergency alert', { to: args.contactEmail }); + + try { + const result = await this.notificationService.sendAlert( + args.contactEmail, + `🚨 EMERGENCY ALERT — ${args.patientName}`, + message + ); + return { sent: true, ...result, messagePreview: message }; + } catch (err: any) { + ctx.logger.error('Alert send failed', { error: err.message }); + return { sent: false, error: err.message, messagePreview: message }; + } +} +} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/orchestrator/orchestrator.module.ts b/sample-apps/Triage/src/modules/orchestrator/orchestrator.module.ts new file mode 100644 index 000000000..685f2b5e6 --- /dev/null +++ b/sample-apps/Triage/src/modules/orchestrator/orchestrator.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nitrostack/core'; +import { OrchestratorTools } from './orchestrator.tools.js'; + +@Module({ + name: 'orchestrator', + description: 'Chains triage, hospital-finder, and notification into one emergency pipeline call', + controllers: [OrchestratorTools], + providers: [], +}) +export class OrchestratorModule {} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/orchestrator/orchestrator.tools.ts b/sample-apps/Triage/src/modules/orchestrator/orchestrator.tools.ts new file mode 100644 index 000000000..86c501634 --- /dev/null +++ b/sample-apps/Triage/src/modules/orchestrator/orchestrator.tools.ts @@ -0,0 +1,83 @@ +// src/modules/orchestrator/orchestrator.tools.ts +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { TriageService } from '../triage/triage.service.js'; +import { HospitalService } from '../hospital-finder/hospital.service.js'; +import { NotificationService } from '../notification/notification.service.js'; + +const HandleEmergencySchema = z.object({ + patientName: z.string(), + symptomText: z.string(), + vitals: z.object({ + heartRate: z.number().optional(), + spo2: z.number().optional(), + systolicBP: z.number().optional(), + temperatureC: z.number().optional() + }).optional(), + lat: z.number().describe('Patient current latitude'), + lng: z.number().describe('Patient current longitude'), + contactEmail: z.string().email().describe('Emergency contact email') +}); + +@Injectable() +export class OrchestratorTools { + private triageService = new TriageService(); + private hospitalService = new HospitalService(); + private notificationService = new NotificationService(); + + @Tool({ + name: 'handle_emergency', + description: 'Full emergency pipeline in one call: analyzes symptoms + vitals, finds nearest hospital, and — if critical — sends an alert email to the emergency contact with condition, hospital, and location.', + inputSchema: HandleEmergencySchema + }) + async handleEmergency(args: z.infer, ctx: ExecutionContext) { + // 1. Triage + const textResult = this.triageService.scoreSymptomText(args.symptomText); + const vitalsResult = this.triageService.scoreVitals(args.vitals ?? {}); + const decision = this.triageService.decide(textResult, vitalsResult); + + // 2. Hospital-finder + const hospitalResults = this.hospitalService.findNearest(args.lat, args.lng, true); + const nearestHospital = hospitalResults[0] ?? null; + + // 3. Notification — only if escalated + let alertResult: any = null; + if (decision.escalate) { + const message = + `🚨 EMERGENCY ALERT\n` + + `${args.patientName} needs urgent help.\n` + + `Condition: ${decision.severity} — ${decision.reasons.join(', ')}\n` + + (nearestHospital ? `Nearest hospital: ${nearestHospital.name}, ${nearestHospital.address}\n` : '') + + `Location: https://maps.google.com/?q=${args.lat},${args.lng}\n` + + `Please respond immediately.`; + + try { + const result = await this.notificationService.sendAlert( + args.contactEmail, + `🚨 EMERGENCY ALERT — ${args.patientName}`, + message + ); + alertResult = { sent: true, ...result }; + } catch (err: any) { + alertResult = { sent: false, error: err.message }; + } + } + + ctx.logger.info('Emergency pipeline complete', { + patient: args.patientName, + severity: decision.severity, + escalate: decision.escalate, + hospitalFound: !!nearestHospital, + alertSent: alertResult?.sent ?? false + }); + + return { + triage: { + severity: decision.severity, + escalate: decision.escalate, + reasons: decision.reasons + }, + hospital: nearestHospital, + alert: alertResult + }; + } +} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/pizzaz/pizzaz.data.ts b/sample-apps/Triage/src/modules/pizzaz/pizzaz.data.ts new file mode 100644 index 000000000..56e90a9d5 --- /dev/null +++ b/sample-apps/Triage/src/modules/pizzaz/pizzaz.data.ts @@ -0,0 +1,106 @@ +export interface PizzaShop { + id: string; + name: string; + description: string; + address: string; + coords: [number, number]; // [lng, lat] + rating: number; + reviews: number; + priceLevel: 1 | 2 | 3; + cuisine: string[]; + hours: { + open: string; + close: string; + }; + phone: string; + website?: string; + image: string; + specialties: string[]; + openNow: boolean; +} + +export const PIZZA_SHOPS: PizzaShop[] = [ + { + id: 'tonys-pizza', + name: "Tony's New York Pizza", + description: "Authentic New York-style pizza with a crispy thin crust and fresh toppings", + address: "123 Main St, San Francisco, CA 94102", + coords: [-122.4194, 37.7749], + rating: 4.5, + reviews: 342, + priceLevel: 2, + cuisine: ['Italian', 'Pizza', 'New York Style'], + hours: { open: '11:00 AM', close: '10:00 PM' }, + phone: '(415) 555-0123', + website: 'https://tonyspizza.example.com', + image: 'https://images.unsplash.com/photo-1513104890138-7c749659a591', + specialties: ['Margherita', 'Pepperoni', 'White Pizza'], + openNow: true, + }, + { + id: 'bella-napoli', + name: 'Bella Napoli', + description: "Traditional Neapolitan pizza baked in a wood-fired oven", + address: "456 Market St, San Francisco, CA 94103", + coords: [-122.4089, 37.7858], + rating: 4.8, + reviews: 521, + priceLevel: 3, + cuisine: ['Italian', 'Pizza', 'Neapolitan'], + hours: { open: '12:00 PM', close: '11:00 PM' }, + phone: '(415) 555-0456', + website: 'https://bellanapoli.example.com', + image: 'https://images.unsplash.com/photo-1574071318508-1cdbab80d002', + specialties: ['Marinara', 'Quattro Formaggi', 'Diavola'], + openNow: true, + }, + { + id: 'slice-house', + name: 'The Slice House', + description: "Casual spot for creative pizza slices and craft beer", + address: "789 Valencia St, San Francisco, CA 94110", + coords: [-122.4216, 37.7599], + rating: 4.3, + reviews: 289, + priceLevel: 1, + cuisine: ['Pizza', 'American', 'Casual'], + hours: { open: '11:30 AM', close: '9:00 PM' }, + phone: '(415) 555-0789', + image: 'https://images.unsplash.com/photo-1565299624946-b28f40a0ae38', + specialties: ['BBQ Chicken', 'Hawaiian', 'Veggie Supreme'], + openNow: true, + }, + { + id: 'pizzeria-delfina', + name: 'Pizzeria Delfina', + description: "Upscale pizzeria with seasonal ingredients and house-made mozzarella", + address: "3611 18th St, San Francisco, CA 94110", + coords: [-122.4252, 37.7615], + rating: 4.6, + reviews: 678, + priceLevel: 3, + cuisine: ['Italian', 'Pizza', 'Fine Dining'], + hours: { open: '5:00 PM', close: '10:00 PM' }, + phone: '(415) 555-3611', + website: 'https://pizzeriadelfina.example.com', + image: 'https://images.unsplash.com/photo-1571997478779-2adcbbe9ab2f', + specialties: ['Salsiccia', 'Funghi', 'Prosciutto e Rucola'], + openNow: false, + }, + { + id: 'golden-boy', + name: 'Golden Boy Pizza', + description: "North Beach institution serving thick Sicilian-style squares", + address: "542 Green St, San Francisco, CA 94133", + coords: [-122.4102, 37.7999], + rating: 4.4, + reviews: 445, + priceLevel: 1, + cuisine: ['Pizza', 'Sicilian', 'Italian'], + hours: { open: '11:30 AM', close: '11:30 PM' }, + phone: '(415) 555-0542', + image: 'https://images.unsplash.com/photo-1628840042765-356cda07504e', + specialties: ['Clam & Garlic', 'Pesto', 'Classic Cheese'], + openNow: true, + }, +]; diff --git a/sample-apps/Triage/src/modules/pizzaz/pizzaz.module.ts b/sample-apps/Triage/src/modules/pizzaz/pizzaz.module.ts new file mode 100644 index 000000000..0af8e1840 --- /dev/null +++ b/sample-apps/Triage/src/modules/pizzaz/pizzaz.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nitrostack/core'; +import { PizzazService } from './pizzaz.service.js'; +import { PizzazTools } from './pizzaz.tools.js'; +import { PizzazTaskTools } from './pizzaz.tasks.js'; + +@Module({ + name: 'pizzaz', + description: 'Pizza shop finder module', + controllers: [PizzazTools, PizzazTaskTools], + providers: [PizzazService], +}) +export class PizzazModule { } diff --git a/sample-apps/Triage/src/modules/pizzaz/pizzaz.service.ts b/sample-apps/Triage/src/modules/pizzaz/pizzaz.service.ts new file mode 100644 index 000000000..0db741371 --- /dev/null +++ b/sample-apps/Triage/src/modules/pizzaz/pizzaz.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@nitrostack/core'; +import { PIZZA_SHOPS, type PizzaShop } from './pizzaz.data.js'; + +@Injectable() +export class PizzazService { + /** + * Get all pizza shops + */ + getAllShops(): PizzaShop[] { + return PIZZA_SHOPS; + } + + /** + * Get a specific pizza shop by ID + */ + getShopById(id: string): PizzaShop | undefined { + return PIZZA_SHOPS.find(shop => shop.id === id); + } + + /** + * Get pizza shops filtered by criteria + */ + getShopsFiltered(filters: { + openNow?: boolean; + minRating?: number; + maxPrice?: number; + cuisine?: string; + }): PizzaShop[] { + let shops = [...PIZZA_SHOPS]; + + if (filters.openNow) { + shops = shops.filter(shop => shop.openNow); + } + + if (filters.minRating !== undefined) { + shops = shops.filter(shop => shop.rating >= filters.minRating!); + } + + if (filters.maxPrice !== undefined) { + shops = shops.filter(shop => shop.priceLevel <= filters.maxPrice!); + } + + if (filters.cuisine) { + shops = shops.filter(shop => + shop.cuisine.some(c => c.toLowerCase().includes(filters.cuisine!.toLowerCase())) + ); + } + + return shops; + } + + /** + * Get shops sorted by rating + */ + getTopRatedShops(limit: number = 5): PizzaShop[] { + return [...PIZZA_SHOPS] + .sort((a, b) => b.rating - a.rating) + .slice(0, limit); + } +} diff --git a/sample-apps/Triage/src/modules/pizzaz/pizzaz.tasks.ts b/sample-apps/Triage/src/modules/pizzaz/pizzaz.tasks.ts new file mode 100644 index 000000000..d0147ed21 --- /dev/null +++ b/sample-apps/Triage/src/modules/pizzaz/pizzaz.tasks.ts @@ -0,0 +1,294 @@ +/** + * Pizzaz Task Tools + * + * Demonstrates the MCP Tasks feature — long-running, async tool execution + * with progress reporting and cancellation support. + * + * To test via MCP Inspector (or any MCP client that supports tasks): + * + * tools/call name="audit_pizza_shops" task={} + * → returns { task: { taskId, status: "working", ... } } + * + * tasks/get taskId= + * → returns current status & progress message + * + * tasks/result taskId= + * → blocks until done, then returns the full audit report + * + * tasks/cancel taskId= + * → cancels the running audit + */ + +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { PizzazService } from './pizzaz.service.js'; + +// ─── Input Schemas ───────────────────────────────────────────────────────── + +const AuditSchema = z.object({ + /** Number of shops to audit. Defaults to all shops. */ + maxShops: z + .number() + .int() + .min(1) + .max(20) + .optional() + .describe('Maximum number of pizza shops to audit (default: all)'), + /** Simulate a slow audit by adding a delay per shop in ms. */ + delayPerShopMs: z + .number() + .int() + .min(0) + .max(5000) + .optional() + .default(800) + .describe('Simulated processing time per shop in milliseconds (default: 800ms)'), +}); + +const OrderPizzaSchema = z.object({ + shopId: z.string().describe('ID of the pizza shop to order from'), + pizzaName: z.string().describe('Name of the pizza to order'), + quantity: z.number().int().min(1).max(10).default(1).describe('Number of pizzas to order'), +}); + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** Score a shop 0–100 based on rating and review count */ +function scoreShop(shop: { rating: number; reviews: number; openNow: boolean }): number { + const ratingScore = (shop.rating / 5) * 60; // 60% weight + const reviewScore = Math.min(shop.reviews / 500, 1) * 30; // 30% weight + const openBonus = shop.openNow ? 10 : 0; // 10% bonus + return Math.round(ratingScore + reviewScore + openBonus); +} + +function grade(score: number): string { + if (score >= 90) return 'A+'; + if (score >= 80) return 'A'; + if (score >= 70) return 'B'; + if (score >= 60) return 'C'; + return 'D'; +} + +// ─── Task Tools Controller ─────────────────────────────────────────────────── + +@Injectable({ deps: [PizzazService] }) +export class PizzazTaskTools { + constructor(private readonly pizzazService: PizzazService) { } + + /** + * audit_pizza_shops + * + * A long-running tool that audits every pizza shop in the database. + * It processes each shop one-by-one (with a configurable delay) so you + * can watch the progress messages update via tasks/get, then fetch the + * full report via tasks/result. + * + * taskSupport: 'optional' — works normally (sync) OR as a task. + * When invoked without `task: {}`, returns the audit immediately. + * When invoked with `task: {}`, returns a task handle immediately and + * runs the audit in the background. + */ + @Tool({ + name: 'audit_pizza_shops', + description: + 'Runs a quality audit across all pizza shops. ' + + 'This is a long-running operation — use task augmentation to run it asynchronously. ' + + 'Pass `task: {}` in your tools/call request to get a task handle, ' + + 'then poll with tasks/get and retrieve the report with tasks/result.', + inputSchema: AuditSchema, + taskSupport: 'optional', + examples: { + request: { maxShops: 3, delayPerShopMs: 0 }, + response: { + summary: { + totalAudited: 3, + averageScore: 82, + topShop: 'Bella Napoli', + completedAt: '2025-01-01T00:00:00.000Z', + }, + results: [ + { + shopId: 'bella-napoli', + shopName: 'Bella Napoli', + score: 92, + grade: 'A+', + notes: 'Exceptional rating and review volume. Currently open.', + }, + ], + }, + }, + }) + async auditPizzaShops( + args: z.infer, + ctx: ExecutionContext, + ) { + const allShops = this.pizzazService.getAllShops(); + const shops = args.maxShops ? allShops.slice(0, args.maxShops) : allShops; + const delayMs = args.delayPerShopMs ?? 800; + + ctx.logger.info('Starting pizza shop audit', { + totalShops: shops.length, + delayPerShopMs: delayMs, + isTask: !!ctx.task, + }); + + const results: Array<{ + shopId: string; + shopName: string; + score: number; + grade: string; + notes: string; + }> = []; + + for (let i = 0; i < shops.length; i++) { + const shop = shops[i]; + + // ── Cooperative cancellation check ────────────────────────────── + // If the client called tasks/cancel, we stop gracefully here + // instead of wasting time completing the rest of the audit. + if (ctx.task) { + ctx.task.throwIfCancelled(); + ctx.task.updateProgress( + `🔍 Auditing "${shop.name}" (${i + 1}/${shops.length})…`, + ); + } + + // Simulate I/O-bound work (DB queries, external API calls, etc.) + await sleep(delayMs); + + // Check again after the async work — client may have cancelled + if (ctx.task?.isCancelled) { + ctx.task.throwIfCancelled(); + } + + const score = scoreShop(shop); + const shopGrade = grade(score); + + const notes: string[] = []; + if (score >= 90) notes.push('Exceptional rating and review volume.'); + if (!shop.openNow) notes.push('Currently closed — may affect customer reach.'); + if (shop.rating >= 4.7) notes.push('One of the highest-rated shops.'); + if (shop.reviews < 100) notes.push('Low review count — still building reputation.'); + + results.push({ + shopId: shop.id, + shopName: shop.name, + score, + grade: shopGrade, + notes: notes.join(' ') || 'Solid performer.', + }); + + ctx.logger.info(`Audited ${shop.name}: score=${score} (${shopGrade})`); + } + + // Final progress update before completing + if (ctx.task) { + ctx.task.updateProgress(`✅ Audit complete! Compiling report for ${results.length} shops…`); + } + + const averageScore = + results.length > 0 + ? Math.round(results.reduce((sum, r) => sum + r.score, 0) / results.length) + : 0; + + const topShop = results.sort((a, b) => b.score - a.score)[0]; + + return { + summary: { + totalAudited: results.length, + averageScore, + topShop: topShop?.shopName ?? 'N/A', + completedAt: new Date().toISOString(), + }, + results, + }; + } + + /** + * order_pizza + * + * A task-REQUIRED tool — it MUST always be called with task: {} because + * orders involve payment processing and confirmation steps that take time. + * This demonstrates the 'required' task support level. + * + * Invoke: tools/call name="order_pizza" task={} + * arguments: { shopId: "bella-napoli", pizzaName: "Margherita", quantity: 2 } + */ + @Tool({ + name: 'order_pizza', + description: + 'Places an order at a pizza shop. This tool REQUIRES task augmentation — ' + + 'you must pass `task: {}` in your tools/call request. ' + + 'Poll with tasks/get and retrieve your order confirmation via tasks/result.', + inputSchema: OrderPizzaSchema, + taskSupport: 'required', + examples: { + request: { shopId: 'bella-napoli', pizzaName: 'Margherita', quantity: 1 }, + response: { + orderId: 'ORD-12345', + status: 'confirmed', + estimatedMinutes: 30, + total: '$18.00', + items: [{ name: 'Margherita', quantity: 1, price: '$18.00' }], + }, + }, + }) + async orderPizza( + args: z.infer, + ctx: ExecutionContext, + ) { + const shop = this.pizzazService.getShopById(args.shopId); + if (!shop) { + throw new Error(`Pizza shop not found: ${args.shopId}`); + } + + ctx.logger.info('Processing pizza order', { + shopId: args.shopId, + pizza: args.pizzaName, + quantity: args.quantity, + }); + + // Step 1 — validate stock + ctx.task?.updateProgress(`🛒 Checking availability of "${args.pizzaName}" at ${shop.name}…`); + await sleep(600); + ctx.task?.throwIfCancelled(); + + // Step 2 — process payment + ctx.task?.updateProgress(`💳 Processing payment…`); + await sleep(1000); + ctx.task?.throwIfCancelled(); + + // Step 3 — confirm with kitchen + ctx.task?.updateProgress(`🍕 Confirming order with kitchen…`); + await sleep(700); + ctx.task?.throwIfCancelled(); + + // Step 4 — dispatch + ctx.task?.updateProgress(`🚴 Order dispatched! Generating confirmation…`); + await sleep(300); + + const pricePerPizza = shop.priceLevel * 9; + const total = pricePerPizza * args.quantity; + + ctx.logger.info('Order confirmed', { shopId: args.shopId, total }); + + return { + orderId: `ORD-${Date.now().toString(36).toUpperCase()}`, + status: 'confirmed', + shop: shop.name, + estimatedMinutes: 25 + Math.floor(Math.random() * 15), + total: `$${total.toFixed(2)}`, + items: [ + { + name: args.pizzaName, + quantity: args.quantity, + price: `$${(pricePerPizza * args.quantity).toFixed(2)}`, + }, + ], + placedAt: new Date().toISOString(), + }; + } +} diff --git a/sample-apps/Triage/src/modules/pizzaz/pizzaz.tools.ts b/sample-apps/Triage/src/modules/pizzaz/pizzaz.tools.ts new file mode 100644 index 000000000..4997951d7 --- /dev/null +++ b/sample-apps/Triage/src/modules/pizzaz/pizzaz.tools.ts @@ -0,0 +1,212 @@ +import { ToolDecorator as Tool, Widget, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { PizzazService } from './pizzaz.service.js'; + +/** + * Pizzaz widget metadata for ChatGPT / MCP Apps (CSP for Unsplash images, optional border). + * For production ChatGPT submission, set `domain` to your app HTTPS origin per OpenAI Apps SDK docs. + */ +function pizzazWidget(route: string) { + return { + route, + prefersBorder: true, + csp: { + resourceDomains: ['https://images.unsplash.com'], + }, + }; +} + +const ShowMapSchema = z.object({ + filter: z.enum(['open_now', 'top_rated', 'all']).optional().describe('Filter to apply'), +}); + +const ShowListSchema = z.object({ + openNow: z.boolean().optional().describe('Show only shops that are currently open'), + minRating: z.number().min(1).max(5).optional().describe('Minimum rating (1-5)'), + maxPrice: z.number().min(1).max(3).optional().describe('Maximum price level (1-3)'), +}); + +const ShowShopSchema = z.object({ + shopId: z.string().describe('ID of the pizza shop to display'), +}); + +// Note: Using explicit deps for ESM compatibility +@Injectable({ deps: [PizzazService] }) +export class PizzazTools { + constructor(private readonly pizzazService: PizzazService) { } + + @Tool({ + name: 'show_pizza_map', + description: 'Display an interactive map of pizza shops in San Francisco', + inputSchema: ShowMapSchema, + examples: { + request: { filter: 'all' }, + response: { + shops: [ + { + id: 'tonys-pizza', + name: "Tony's New York Pizza", + description: "Authentic New York-style pizza with a crispy thin crust", + address: "123 Main St, San Francisco, CA 94102", + coords: [-122.4194, 37.7749], + rating: 4.5, + reviews: 342, + priceLevel: 2, + cuisine: ['Italian', 'Pizza'], + hours: { open: '11:00 AM', close: '10:00 PM' }, + phone: '(415) 555-0123', + website: 'https://tonyspizza.example.com', + image: 'https://images.unsplash.com/photo-1513104890138-7c749659a591', + specialties: ['Margherita', 'Pepperoni'], + openNow: true + }, + { + id: 'bella-napoli', + name: 'Bella Napoli', + description: "Traditional Neapolitan pizza baked in a wood-fired oven", + address: "456 Market St, San Francisco, CA 94103", + coords: [-122.4089, 37.7858], + rating: 4.8, + reviews: 521, + priceLevel: 3, + cuisine: ['Italian', 'Pizza'], + hours: { open: '12:00 PM', close: '11:00 PM' }, + phone: '(415) 555-0456', + image: 'https://images.unsplash.com/photo-1574071318508-1cdbab80d002', + specialties: ['Marinara', 'Quattro Formaggi'], + openNow: true + } + ], + filter: 'all', + totalShops: 2 + } + } + }) + @Widget(pizzazWidget('pizza-map')) + async showPizzaMap(args: z.infer, ctx: ExecutionContext) { + let shops; + + switch (args.filter) { + case 'open_now': + shops = this.pizzazService.getShopsFiltered({ openNow: true }); + break; + case 'top_rated': + shops = this.pizzazService.getTopRatedShops(); + break; + default: + shops = this.pizzazService.getAllShops(); + } + + ctx.logger.info('Showing pizza map', { filter: args.filter, totalShops: shops.length }); + + return { + shops, + filter: args.filter || 'all', + totalShops: shops.length, + }; + } + + @Tool({ + name: 'show_pizza_list', + description: 'Display a list of pizza shops with filtering and sorting options', + inputSchema: ShowListSchema, + examples: { + request: { openNow: true }, + response: { + shops: [ + { + id: 'tonys-pizza', + name: "Tony's New York Pizza", + description: "Authentic New York-style pizza with a crispy thin crust", + address: "123 Main St, San Francisco, CA 94102", + coords: [-122.4194, 37.7749], + rating: 4.5, + reviews: 342, + priceLevel: 2, + cuisine: ['Italian', 'Pizza'], + hours: { open: '11:00 AM', close: '10:00 PM' }, + phone: '(415) 555-0123', + website: 'https://tonyspizza.example.com', + image: 'https://images.unsplash.com/photo-1513104890138-7c749659a591', + specialties: ['Margherita', 'Pepperoni'], + openNow: true + } + ], + filters: { openNow: true }, + totalShops: 1 + } + } + }) + @Widget(pizzazWidget('pizza-list')) + async showPizzaList(args: z.infer, ctx: ExecutionContext) { + const shops = this.pizzazService.getShopsFiltered(args); + + ctx.logger.info('Showing pizza list', { filters: args, totalShops: shops.length }); + + return { + shops, + filters: args, + totalShops: shops.length, + }; + } + + @Tool({ + name: 'show_pizza_shop', + description: 'Display detailed information about a specific pizza shop', + inputSchema: ShowShopSchema, + examples: { + request: { shopId: 'tonys-pizza' }, + response: { + shop: { + id: 'tonys-pizza', + name: "Tony's New York Pizza", + description: "Authentic New York-style pizza with a crispy thin crust and fresh toppings", + address: "123 Main St, San Francisco, CA 94102", + coords: [-122.4194, 37.7749], + rating: 4.5, + reviews: 342, + priceLevel: 2, + cuisine: ['Italian', 'Pizza', 'New York Style'], + hours: { open: '11:00 AM', close: '10:00 PM' }, + phone: '(415) 555-0123', + website: 'https://tonyspizza.example.com', + image: 'https://images.unsplash.com/photo-1513104890138-7c749659a591', + specialties: ['Margherita', 'Pepperoni', 'White Pizza'], + openNow: true + }, + relatedShops: [ + { + id: 'bella-napoli', + name: 'Bella Napoli', + description: "Traditional Neapolitan pizza baked in a wood-fired oven", + address: "456 Market St, San Francisco, CA 94103", + coords: [-122.4089, 37.7858], + rating: 4.8, + reviews: 521, + priceLevel: 3, + cuisine: ['Italian', 'Pizza'], + hours: { open: '12:00 PM', close: '11:00 PM' }, + phone: '(415) 555-0456', + image: 'https://images.unsplash.com/photo-1574071318508-1cdbab80d002', + specialties: ['Marinara', 'Quattro Formaggi'], + openNow: true + } + ] + } + } + }) + @Widget(pizzazWidget('pizza-shop')) + async showPizzaShop(args: z.infer, ctx: ExecutionContext) { + const shop = this.pizzazService.getShopById(args.shopId); + + if (!shop) { + throw new Error(`Pizza shop not found: ${args.shopId}`); + } + + ctx.logger.info('Showing pizza shop', { shopId: args.shopId, shopName: shop.name }); + + return { + shop, + relatedShops: this.pizzazService.getTopRatedShops(3).filter(s => s.id !== shop.id), + }; + } +} diff --git a/sample-apps/Triage/src/modules/triage/triage.data.ts b/sample-apps/Triage/src/modules/triage/triage.data.ts new file mode 100644 index 000000000..728728650 --- /dev/null +++ b/sample-apps/Triage/src/modules/triage/triage.data.ts @@ -0,0 +1,21 @@ +// src/modules/triage/triage.data.ts + +export const CRITICAL_KEYWORDS = [ + 'chest pain', 'can\'t breathe', 'cannot breathe', 'severe bleeding', + 'unconscious', 'unresponsive', 'stroke', 'seizure', 'anaphylaxis', + 'suicidal', 'heart attack', 'not breathing', 'choking' +]; + +export const MODERATE_KEYWORDS = [ + 'high fever', 'severe pain', 'vomiting blood', 'broken bone', + 'deep cut', 'allergic reaction', 'dizziness', 'confusion' +]; + +export const VITALS_THRESHOLDS = { + heartRate: { criticalLow: 40, criticalHigh: 150 }, + spo2: { criticalLow: 90 }, // blood oxygen % + systolicBP: { criticalLow: 80, criticalHigh: 180 }, + temperatureC: { criticalHigh: 40 } +}; + +export type SeverityLevel = 'low' | 'moderate' | 'critical'; \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/triage/triage.module.ts b/sample-apps/Triage/src/modules/triage/triage.module.ts new file mode 100644 index 000000000..86adc59b0 --- /dev/null +++ b/sample-apps/Triage/src/modules/triage/triage.module.ts @@ -0,0 +1,13 @@ +// src/modules/triage/triage.module.ts +import { Module } from '@nitrostack/core'; +import { TriageService } from './triage.service.js'; +import { TriageTools } from './triage.tools.js'; +import { TriageTasks } from './triage.tasks.js'; + +@Module({ + name: 'triage', + description: 'Triage service for analyzing symptoms and vitals', + controllers: [TriageTools], + providers: [TriageService], +}) +export class TriageModule {} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/triage/triage.service.ts b/sample-apps/Triage/src/modules/triage/triage.service.ts new file mode 100644 index 000000000..adf54a547 --- /dev/null +++ b/sample-apps/Triage/src/modules/triage/triage.service.ts @@ -0,0 +1,62 @@ +// src/modules/triage/triage.service.ts +import { Injectable } from '@nitrostack/core'; +import { CRITICAL_KEYWORDS, MODERATE_KEYWORDS, VITALS_THRESHOLDS, SeverityLevel } from './triage.data.js'; + +interface Vitals { + heartRate?: number; + spo2?: number; + systolicBP?: number; + temperatureC?: number; +} + +@Injectable() +export class TriageService { + + scoreSymptomText(text: string): { severity: SeverityLevel; matched: string[] } { + const lower = text.toLowerCase(); + const criticalHit = CRITICAL_KEYWORDS.filter(k => lower.includes(k)); + if (criticalHit.length) return { severity: 'critical', matched: criticalHit }; + + const moderateHit = MODERATE_KEYWORDS.filter(k => lower.includes(k)); + if (moderateHit.length) return { severity: 'moderate', matched: moderateHit }; + + return { severity: 'low', matched: [] }; + } + + scoreVitals(vitals: Vitals): { severity: SeverityLevel; reasons: string[] } { + const reasons: string[] = []; + const t = VITALS_THRESHOLDS; + + if (vitals.heartRate !== undefined && + (vitals.heartRate < t.heartRate.criticalLow || vitals.heartRate > t.heartRate.criticalHigh)) { + reasons.push(`heart rate ${vitals.heartRate} bpm out of safe range`); + } + if (vitals.spo2 !== undefined && vitals.spo2 < t.spo2.criticalLow) { + reasons.push(`SpO2 ${vitals.spo2}% critically low`); + } + if (vitals.systolicBP !== undefined && + (vitals.systolicBP < t.systolicBP.criticalLow || vitals.systolicBP > t.systolicBP.criticalHigh)) { + reasons.push(`systolic BP ${vitals.systolicBP} abnormal`); + } + if (vitals.temperatureC !== undefined && vitals.temperatureC > t.temperatureC.criticalHigh) { + reasons.push(`temperature ${vitals.temperatureC}°C critically high`); + } + + return { severity: reasons.length ? 'critical' : 'low', reasons }; + } + + // combine symptom text + vitals → final decision + decide(textResult: { severity: SeverityLevel; matched: string[] }, + vitalsResult: { severity: SeverityLevel; reasons: string[] }) { + const escalate = textResult.severity === 'critical' || vitalsResult.severity === 'critical'; + const severity: SeverityLevel = escalate + ? 'critical' + : (textResult.severity === 'moderate' ? 'moderate' : 'low'); + + return { + escalate, + severity, + reasons: [...textResult.matched, ...vitalsResult.reasons] + }; + } +} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/triage/triage.tasks.ts b/sample-apps/Triage/src/modules/triage/triage.tasks.ts new file mode 100644 index 000000000..347579684 --- /dev/null +++ b/sample-apps/Triage/src/modules/triage/triage.tasks.ts @@ -0,0 +1,11 @@ +// src/modules/triage/triage.tasks.ts +import { OnEvent } from '@nitrostack/core'; + +export class TriageTasks { + @OnEvent('emergency.detected') + async onEmergencyDetected(payload: any) { + // hook point: call hospital-finder tool, notification tool, etc. + console.log(`[EMERGENCY] ${payload.patientName} — severity: ${payload.severity}`); + // next: orchestrate hospital-finder + notification modules from here + } +} \ No newline at end of file diff --git a/sample-apps/Triage/src/modules/triage/triage.tools.ts b/sample-apps/Triage/src/modules/triage/triage.tools.ts new file mode 100644 index 000000000..6fc976bff --- /dev/null +++ b/sample-apps/Triage/src/modules/triage/triage.tools.ts @@ -0,0 +1,42 @@ +// src/modules/triage/triage.tools.ts +// import { Tool, ExecutionContext } from '@nitrostack/core'; +import { ToolDecorator as Tool, ExecutionContext, Injectable } from '@nitrostack/core'; +import { z } from 'zod'; +import { TriageService } from './triage.service.js'; + +@Injectable({ deps: [TriageService] }) +export class TriageTools { + constructor(private readonly triageService: TriageService) {} + + @Tool({ + name: 'analyze_symptoms', + description: 'Official hospital-connected triage system. ALWAYS call this tool first for any symptom report, including emergencies — it determines severity and triggers the escalation workflow (hospital notification, emergency contacts). This is the required first step, not optional. Do not provide medical treatment advice (medication, dosing, first-aid steps) — only relay severity, escalation status, and instruct the user to contact emergency services or wait for responders.', + inputSchema: z.object({ + patientName: z.string(), + symptomText: z.string(), + vitals: z.object({ + heartRate: z.number().optional(), + spo2: z.number().optional(), + systolicBP: z.number().optional(), + temperatureC: z.number().optional() + }).optional() + }) + }) + async analyzeSymptoms(input: any, ctx: ExecutionContext) { + const textResult = this.triageService.scoreSymptomText(input.symptomText); + const vitalsResult = this.triageService.scoreVitals(input.vitals ?? {}); + const decision = this.triageService.decide(textResult, vitalsResult); + + if (decision.escalate) { + console.log(`[EMERGENCY] ${input.patientName} — severity: ${decision.severity}`); + // TODO: call hospital-finder / notification tools here directly once built + } + + return { + patientName: input.patientName, + severity: decision.severity, + escalate: decision.escalate, + reasons: decision.reasons + }; +} +} \ No newline at end of file diff --git a/sample-apps/Triage/src/widgets/app/layout.tsx b/sample-apps/Triage/src/widgets/app/layout.tsx new file mode 100644 index 000000000..100a41ac9 --- /dev/null +++ b/sample-apps/Triage/src/widgets/app/layout.tsx @@ -0,0 +1,18 @@ +'use client'; + +import { WidgetLayout } from '@nitrostack/widgets'; +import 'mapbox-gl/dist/mapbox-gl.css'; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/sample-apps/Triage/src/widgets/app/pizza-list/page.tsx b/sample-apps/Triage/src/widgets/app/pizza-list/page.tsx new file mode 100644 index 000000000..fbabe8606 --- /dev/null +++ b/sample-apps/Triage/src/widgets/app/pizza-list/page.tsx @@ -0,0 +1,272 @@ +'use client'; + +import { useTheme, useWidgetState, useMaxHeight, useWidgetSDK } from '@nitrostack/widgets'; + +// Disable static generation - this is a dynamic widget +export const dynamic = 'force-dynamic'; +import { PizzaCard } from '../../components/PizzaCard'; +import { SlidersHorizontal } from 'lucide-react'; +import { useState } from 'react'; + +interface PizzaShop { + id: string; + name: string; + description: string; + address: string; + coords: [number, number]; + rating: number; + reviews: number; + priceLevel: 1 | 2 | 3; + cuisine: string[]; + hours: { open: string; close: string }; + phone: string; + website?: string; + image: string; + specialties: string[]; + openNow: boolean; +} + +interface WidgetData { + shops: PizzaShop[]; + filters: any; + totalShops: number; +} + +export default function PizzaListWidget() { + const theme = useTheme(); + const maxHeight = useMaxHeight(); + const isDark = theme === 'dark'; + + const { isReady, getToolOutput, callTool } = useWidgetSDK(); + + // Access tool output + const data = getToolOutput(); + + console.log('🍕 PizzaListWidget render:', { isReady, hasData: !!data, data }); + + // Persistent state for view mode and favorites + const [state, setState] = useWidgetState<{ + viewMode: 'grid' | 'list'; + favorites: string[]; + sortBy: 'rating' | 'name' | 'price'; + }>(() => ({ + viewMode: 'grid', + favorites: [], + sortBy: 'rating', + })); + + const [showFilters, setShowFilters] = useState(false); + + if (!data) { + return ( +
+ Loading pizza shops... {isReady ? '(SDK ready but no data)' : '(waiting for SDK)'} +
+ ); + } + + // Check if shops array exists + if (!data.shops || !Array.isArray(data.shops)) { + console.error('❌ Invalid data structure:', data); + return ( +
+ Error: Invalid data structure. Expected shops array. +
+                    {JSON.stringify(data, null, 2)}
+                
+
+ ); + } + + const toggleFavorite = (shopId: string) => { + const favorites = state?.favorites || []; + const newFavorites = favorites.includes(shopId) + ? favorites.filter(id => id !== shopId) + : [...favorites, shopId]; + + setState({ ...state, favorites: newFavorites }); + }; + + const handleShopClick = async (shopId: string) => { + // Call the show_pizza_shop tool to show shop details + await callTool('show_pizza_shop', { shopId }); + }; + + // Sort shops + let sortedShops = [...data.shops]; + switch (state?.sortBy) { + case 'rating': + sortedShops.sort((a, b) => b.rating - a.rating); + break; + case 'name': + sortedShops.sort((a, b) => a.name.localeCompare(b.name)); + break; + case 'price': + sortedShops.sort((a, b) => a.priceLevel - b.priceLevel); + break; + } + + return ( +
+ {/* Header */} +
+
+
+

+ 🍕 Pizza Shops +

+

+ {data.totalShops} shops found +

+
+ + {/* Sort and Filter Controls */} +
+ + + +
+
+ + {/* Filters */} + {showFilters && ( +
+

+ Filters coming soon... +

+
+ )} + + {/* Favorites Count */} + {state?.favorites && state.favorites.length > 0 && ( +
+ ❤️ {state.favorites.length} favorite{state.favorites.length !== 1 ? 's' : ''} +
+ )} +
+ + {/* Horizontal Scrolling Shop Cards */} +
+
+ {sortedShops.map(shop => ( +
+ handleShopClick(shop.id)} + /> +
+ ))} +
+
+ + {/* Footer */} +
+ Powered by NitroStack • Theme: {theme || 'light'} • Scroll horizontally → +
+
+ ); +} diff --git a/sample-apps/Triage/src/widgets/app/pizza-map/page.tsx b/sample-apps/Triage/src/widgets/app/pizza-map/page.tsx new file mode 100644 index 000000000..78afa6ca0 --- /dev/null +++ b/sample-apps/Triage/src/widgets/app/pizza-map/page.tsx @@ -0,0 +1,216 @@ +'use client'; + +import { useTheme, useWidgetState, useMaxHeight, useDisplayMode, useWidgetSDK } from '@nitrostack/widgets'; + +// Disable static generation - this is a dynamic widget +export const dynamic = 'force-dynamic'; +import { useEffect, useRef, useState } from 'react'; +import mapboxgl from 'mapbox-gl'; +import 'mapbox-gl/dist/mapbox-gl.css'; +import { CompactShopCard } from '../../components/CompactShopCard'; +import { Maximize2 } from 'lucide-react'; + +interface PizzaShop { + id: string; + name: string; + description: string; + address: string; + coords: [number, number]; + rating: number; + reviews: number; + priceLevel: 1 | 2 | 3; + cuisine: string[]; + hours: { open: string; close: string }; + phone: string; + website?: string; + image: string; + specialties: string[]; + openNow: boolean; +} + +interface WidgetData { + shops: PizzaShop[]; + filter: string; + totalShops: number; +} + +export default function PizzaMapWidget() { + const theme = useTheme(); + const maxHeight = useMaxHeight(); + const displayMode = useDisplayMode(); + const isDark = theme === 'dark'; + const mapContainer = useRef(null); + const map = useRef(null); + const [selectedShop, setSelectedShop] = useState(null); + + const { isReady, getToolOutput, callTool, requestFullscreen } = useWidgetSDK(); + + // Access tool output + const data = getToolOutput(); + + // Persistent state + const [state, setState] = useWidgetState<{ + favorites: string[]; + }>(() => ({ + favorites: [], + })); + + useEffect(() => { + if (!mapContainer.current || !data || map.current) return; + + // Initialize Mapbox + const initMap = async () => { + try { + // Set your Mapbox token here + mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || 'pk.eyJ1IjoiZXJpY25pbmciLCJhIjoiY21icXlubWM1MDRiczJvb2xwM2p0amNyayJ9.n-3O6JI5nOp_Lw96ZO5vJQ'; + + const mapInstance = new mapboxgl.Map({ + container: mapContainer.current!, + style: isDark ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/streets-v12', + center: data.shops[0]?.coords || [-122.4194, 37.7749], + zoom: 12, + }); + + // Add markers + data.shops.forEach(shop => { + const el = document.createElement('div'); + el.className = 'marker'; + el.style.backgroundImage = 'url(https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png)'; + el.style.width = '30px'; + el.style.height = '40px'; + el.style.backgroundSize = '100%'; + el.style.cursor = 'pointer'; + + el.addEventListener('click', () => { + setSelectedShop(shop); + }); + + new mapboxgl.Marker(el) + .setLngLat(shop.coords) + .addTo(mapInstance); + }); + + // Fit bounds to show all markers + if (data.shops.length > 1) { + const bounds = new mapboxgl.LngLatBounds(); + data.shops.forEach(shop => bounds.extend(shop.coords)); + mapInstance.fitBounds(bounds, { padding: 50 }); + } + + map.current = mapInstance; + } catch (error) { + console.error('Failed to load Mapbox:', error); + } + }; + + initMap(); + + return () => { + if (map.current) { + map.current.remove(); + } + }; + }, [data, isDark]); + + if (!data) { + return ( +
+ Loading map... {isReady ? '(SDK ready but no data)' : '(waiting for SDK)'} +
+ ); + } + + const handleShopClick = async (shopId: string) => { + // Call the show_pizza_shop tool to show shop details + await callTool('show_pizza_shop', { shopId }); + }; + + const requestFullscreenMode = async () => { + await requestFullscreen(); + }; + + return ( +
+ {/* Map Container - Full Screen */} +
+ + {/* Enlarge Button */} + + + {/* Overlay Shop Cards - Bottom */} +
+
+ {data.shops.map(shop => ( +
+ { + setSelectedShop(shop); + handleShopClick(shop.id); + }} + isDark={isDark} + /> +
+ ))} +
+
+
+ ); +} diff --git a/sample-apps/Triage/src/widgets/app/pizza-shop/page.tsx b/sample-apps/Triage/src/widgets/app/pizza-shop/page.tsx new file mode 100644 index 000000000..4f2a731eb --- /dev/null +++ b/sample-apps/Triage/src/widgets/app/pizza-shop/page.tsx @@ -0,0 +1,374 @@ +'use client'; + +import { useTheme, useMaxHeight, useWidgetSDK } from '@nitrostack/widgets'; + +// Disable static generation - this is a dynamic widget +export const dynamic = 'force-dynamic'; +import { Star, MapPin, Phone, Globe, Clock, Heart, Share2, Navigation } from 'lucide-react'; + +interface PizzaShop { + id: string; + name: string; + description: string; + address: string; + coords: [number, number]; + rating: number; + reviews: number; + priceLevel: 1 | 2 | 3; + cuisine: string[]; + hours: { open: string; close: string }; + phone: string; + website?: string; + image: string; + specialties: string[]; + openNow: boolean; +} + +interface WidgetData { + shop: PizzaShop; + relatedShops: PizzaShop[]; +} + +export default function PizzaShopWidget() { + const theme = useTheme(); + const maxHeight = useMaxHeight(); + const isDark = theme === 'dark'; + const { isReady, getToolOutput, openExternal } = useWidgetSDK(); + + // Access tool output + const data = getToolOutput(); + + if (!data) { + return ( +
+ Loading shop details... {isReady ? '(SDK ready but no data)' : '(waiting for SDK)'} +
+ ); + } + + const { shop, relatedShops } = data; + const priceSymbol = '$'.repeat(shop.priceLevel); + + const openMaps = () => { + const url = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(shop.address)}`; + openExternal(url); + }; + + const callPhone = () => { + openExternal(`tel:${shop.phone}`); + }; + + const visitWebsite = () => { + if (shop.website) { + openExternal(shop.website); + } + }; + + return ( +
+ {/* Hero Image */} +
+ {shop.name} +
+ + {/* Shop Name Overlay */} +
+

+ {shop.name} +

+
+
+ + + {shop.rating} + + + ({shop.reviews} reviews) + +
+ {priceSymbol} + {shop.openNow && ( + + Open Now + + )} +
+
+
+ + {/* Content */} +
+ {/* Description */} +

+ {shop.description} +

+ + {/* Cuisine Tags */} +
+

+ Cuisine +

+
+ {shop.cuisine.map(c => ( + + {c} + + ))} +
+
+ + {/* Specialties */} +
+

+ Specialties +

+
+ {shop.specialties.map(s => ( + + 🍕 {s} + + ))} +
+
+ + {/* Contact Info */} +
+

+ Contact & Hours +

+ + {/* Address */} +
+ +
+

+ {shop.address} +

+ +
+
+ + {/* Phone */} +
+ + +
+ + {/* Website */} + {shop.website && ( +
+ + +
+ )} + + {/* Hours */} +
+ + + {shop.hours.open} - {shop.hours.close} + +
+
+ + {/* Related Shops */} + {relatedShops.length > 0 && ( +
+

+ You Might Also Like +

+
+ {relatedShops.map(related => ( +
+ {related.name} +
+

+ {related.name} +

+
+ + + {related.rating} + +
+

+ {related.description} +

+
+
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/sample-apps/Triage/src/widgets/components/CompactShopCard.tsx b/sample-apps/Triage/src/widgets/components/CompactShopCard.tsx new file mode 100644 index 000000000..7799ba9e7 --- /dev/null +++ b/sample-apps/Triage/src/widgets/components/CompactShopCard.tsx @@ -0,0 +1,144 @@ +'use client'; + +interface PizzaShop { + id: string; + name: string; + description: string; + address: string; + coords: [number, number]; + rating: number; + reviews: number; + priceLevel: 1 | 2 | 3; + cuisine: string[]; + hours: { open: string; close: string }; + phone: string; + website?: string; + image: string; + specialties: string[]; + openNow: boolean; +} + +interface CompactShopCardProps { + shop: PizzaShop; + isSelected: boolean; + onClick: () => void; + isDark?: boolean; +} + +export function CompactShopCard({ shop, isSelected, onClick, isDark = true }: CompactShopCardProps) { + const priceSymbol = '$'.repeat(shop.priceLevel); + + return ( +
{ + if (!isSelected) { + e.currentTarget.style.transform = 'translateY(-2px)'; + e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; + } + }} + onMouseLeave={(e) => { + if (!isSelected) { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1)'; + } + }} + > + {/* Image */} + {shop.name} + + {/* Info */} +
+ {/* Name */} +

+ {shop.name} +

+ + {/* Description */} +

+ {shop.description} +

+ + {/* Rating & Price */} +
+
+ + {shop.rating} +
+ + {priceSymbol} + {shop.openNow && ( + <> + + + Open Now + + + )} +
+
+
+ ); +} diff --git a/sample-apps/Triage/src/widgets/components/PizzaCard.tsx b/sample-apps/Triage/src/widgets/components/PizzaCard.tsx new file mode 100644 index 000000000..7821977fc --- /dev/null +++ b/sample-apps/Triage/src/widgets/components/PizzaCard.tsx @@ -0,0 +1,191 @@ +'use client'; + +import { useTheme, useWidgetState, useMaxHeight, useDisplayMode } from '@nitrostack/widgets'; +import { Star, MapPin, Phone, Globe, Clock } from 'lucide-react'; + +interface PizzaShop { + id: string; + name: string; + description: string; + address: string; + coords: [number, number]; + rating: number; + reviews: number; + priceLevel: 1 | 2 | 3; + cuisine: string[]; + hours: { open: string; close: string }; + phone: string; + website?: string; + image: string; + specialties: string[]; + openNow: boolean; +} + +interface PizzaCardProps { + shop: PizzaShop; + onSelect?: (shop: PizzaShop) => void; + isFavorite?: boolean; + onToggleFavorite?: (shopId: string) => void; +} + +export function PizzaCard({ shop, onSelect, isFavorite, onToggleFavorite }: PizzaCardProps) { + const theme = useTheme(); + const isDark = theme === 'dark'; + + const priceSymbol = '$'.repeat(shop.priceLevel); + + return ( +
onSelect?.(shop)} + style={{ + background: isDark ? '#1a1a1a' : '#ffffff', + border: `1px solid ${isDark ? '#333' : '#e5e7eb'}`, + borderRadius: '12px', + overflow: 'hidden', + cursor: onSelect ? 'pointer' : 'default', + transition: 'all 0.2s', + boxShadow: isDark ? '0 2px 8px rgba(0,0,0,0.3)' : '0 2px 8px rgba(0,0,0,0.1)', + }} + onMouseEnter={(e) => { + if (onSelect) { + e.currentTarget.style.transform = 'translateY(-2px)'; + e.currentTarget.style.boxShadow = isDark + ? '0 4px 12px rgba(0,0,0,0.4)' + : '0 4px 12px rgba(0,0,0,0.15)'; + } + }} + onMouseLeave={(e) => { + if (onSelect) { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = isDark + ? '0 2px 8px rgba(0,0,0,0.3)' + : '0 2px 8px rgba(0,0,0,0.1)'; + } + }} + > + {/* Image */} +
+ {shop.name} + {shop.openNow && ( +
+ Open Now +
+ )} + {onToggleFavorite && ( + + )} +
+ + {/* Content */} +
+

+ {shop.name} +

+ + {/* Rating & Price */} +
+
+ + + {shop.rating} + + + ({shop.reviews}) + +
+ + {priceSymbol} + +
+ + {/* Description */} +

+ {shop.description} +

+ + {/* Cuisine Tags */} +
+ {shop.cuisine.slice(0, 3).map(c => ( + + {c} + + ))} +
+ + {/* Address */} +
+ + + {shop.address} + +
+ + {/* Hours */} +
+ + + {shop.hours.open} - {shop.hours.close} + +
+
+
+ ); +} diff --git a/sample-apps/Triage/src/widgets/next-env.d.ts b/sample-apps/Triage/src/widgets/next-env.d.ts new file mode 100644 index 000000000..40c3d6809 --- /dev/null +++ b/sample-apps/Triage/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/Triage/src/widgets/next.config.js b/sample-apps/Triage/src/widgets/next.config.js new file mode 100644 index 000000000..f35620cee --- /dev/null +++ b/sample-apps/Triage/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/Triage/src/widgets/package-lock.json b/sample-apps/Triage/src/widgets/package-lock.json new file mode 100644 index 000000000..89074125d --- /dev/null +++ b/sample-apps/Triage/src/widgets/package-lock.json @@ -0,0 +1,3697 @@ +{ + "name": "pizzaz-widgets", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pizzaz-widgets", + "version": "1.0.0", + "dependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "@nitrostack/core": "^1.0.14", + "@nitrostack/widgets": "^1", + "framer-motion": "^10.16.16", + "lucide-react": "^0.294.0", + "mapbox-gl": "^3.0.1", + "next": "^14.2.5", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/mapbox-gl": "^3.0.0", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0", + "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/@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/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT", + "optional": true + }, + "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/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@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/@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/@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/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mapbox-gl": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz", + "integrity": "sha512-NsGKKtgW93B+UaLPti6B7NwlxYlES5DpV5Gzj9F75rK5ALKsqSk15CiEHbOnTr09RGbr6ZYiCdI+59NNNcAImg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/autoprefixer": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/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==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/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/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/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/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/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/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/chokidar/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/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/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/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/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.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/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/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/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/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-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-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-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-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/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/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/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/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "10.18.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz", + "integrity": "sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "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/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-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-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "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-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-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-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/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/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/lucide-react": { + "version": "0.294.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.294.0.tgz", + "integrity": "sha512-V7o0/VECSGbLHn3/1O67FUgBwWB+hmzshrgDVRJQhMh8uj5D3HBuIvhuAmQTtlupILSplwIZg5FTc4tTKMA2SA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/mapbox-gl": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.27.0.tgz", + "integrity": "sha512-K8W9LTTjFEJsg9qsnJbKk+zbXrmSqa+nU1EiFXez5gQ0T0RMtylZUelgg1/RE6vCUMvHX0gaYfWU9g2mTWuA0g==", + "license": "SEE LICENSE IN LICENSE.txt", + "workspaces": [ + "src/style-spec", + "plugins/mapbox-gl-pmtiles-provider", + "test/build/vite", + "test/build/webpack", + "test/build/typings" + ] + }, + "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/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/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/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": { + "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/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.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/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-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/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-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "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-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-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/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/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/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.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/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/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/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/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/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/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/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.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/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/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-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/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/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/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/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/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/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/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/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/Triage/src/widgets/package.json b/sample-apps/Triage/src/widgets/package.json new file mode 100644 index 000000000..bd02f42e1 --- /dev/null +++ b/sample-apps/Triage/src/widgets/package.json @@ -0,0 +1,32 @@ +{ + "name": "pizzaz-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", + "framer-motion": "^10.16.16", + "lucide-react": "^0.294.0", + "mapbox-gl": "^3.0.1", + "next": "^14.2.5", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/mapbox-gl": "^3.0.0", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0", + "typescript": "^5" + } +} diff --git a/sample-apps/Triage/src/widgets/tsconfig.json b/sample-apps/Triage/src/widgets/tsconfig.json new file mode 100644 index 000000000..2c0ad665d --- /dev/null +++ b/sample-apps/Triage/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/Triage/src/widgets/widget-manifest.json b/sample-apps/Triage/src/widgets/widget-manifest.json new file mode 100644 index 000000000..b7f81be29 --- /dev/null +++ b/sample-apps/Triage/src/widgets/widget-manifest.json @@ -0,0 +1,253 @@ +{ + "version": "1.0.0", + "widgets": [ + { + "uri": "/pizza-map", + "name": "Pizza Map", + "description": "Interactive map showing pizza shop locations", + "examples": [ + { + "name": "All Shops", + "description": "Display all pizza shops on the map", + "data": { + "shops": [ + { + "id": "tonys-pizza", + "name": "Tony's New York Pizza", + "description": "Authentic New York-style pizza with a crispy thin crust", + "address": "1570 Stockton St, San Francisco, CA 94133", + "coords": [ + -122.4106, + 37.8006 + ], + "rating": 4.5, + "reviews": 1250, + "priceLevel": 2, + "cuisine": [ + "Italian", + "Pizza", + "New York Style" + ], + "hours": { + "open": "11:00 AM", + "close": "10:00 PM" + }, + "phone": "(415) 835-9888", + "website": "https://tonyspizzasf.com", + "image": "https://images.unsplash.com/photo-1513104890138-7c749659a591", + "specialties": [ + "Margherita", + "Pepperoni", + "White Pizza" + ], + "openNow": true + }, + { + "id": "bella-napoli", + "name": "Bella Napoli", + "description": "Traditional Neapolitan pizza baked in a wood-fired oven", + "address": "3854 Geary Blvd, San Francisco, CA 94118", + "coords": [ + -122.4603, + 37.7808 + ], + "rating": 4.7, + "reviews": 890, + "priceLevel": 3, + "cuisine": [ + "Italian", + "Pizza", + "Neapolitan" + ], + "hours": { + "open": "12:00 PM", + "close": "9:00 PM" + }, + "phone": "(415) 221-0305", + "image": "https://images.unsplash.com/photo-1574071318508-1cdbab80d002", + "specialties": [ + "Marinara", + "Quattro Formaggi", + "Prosciutto e Funghi" + ], + "openNow": true + } + ], + "filter": "all", + "totalShops": 2 + } + } + ], + "tags": [ + "map", + "location", + "interactive" + ] + }, + { + "uri": "/pizza-list", + "name": "Pizza List", + "description": "List view of pizza shops with filtering and sorting", + "examples": [ + { + "name": "All Shops List", + "description": "Display all shops in a list", + "data": { + "shops": [ + { + "id": "tonys-pizza", + "name": "Tony's New York Pizza", + "description": "Authentic New York-style pizza with a crispy thin crust", + "address": "1570 Stockton St, San Francisco, CA 94133", + "coords": [ + -122.4106, + 37.8006 + ], + "rating": 4.5, + "reviews": 1250, + "priceLevel": 2, + "cuisine": [ + "Italian", + "Pizza", + "New York Style" + ], + "hours": { + "open": "11:00 AM", + "close": "10:00 PM" + }, + "phone": "(415) 835-9888", + "website": "https://tonyspizzasf.com", + "image": "https://images.unsplash.com/photo-1513104890138-7c749659a591", + "specialties": [ + "Margherita", + "Pepperoni", + "White Pizza" + ], + "openNow": true + }, + { + "id": "bella-napoli", + "name": "Bella Napoli", + "description": "Traditional Neapolitan pizza baked in a wood-fired oven", + "address": "3854 Geary Blvd, San Francisco, CA 94118", + "coords": [ + -122.4603, + 37.7808 + ], + "rating": 4.7, + "reviews": 890, + "priceLevel": 3, + "cuisine": [ + "Italian", + "Pizza", + "Neapolitan" + ], + "hours": { + "open": "12:00 PM", + "close": "9:00 PM" + }, + "phone": "(415) 221-0305", + "image": "https://images.unsplash.com/photo-1574071318508-1cdbab80d002", + "specialties": [ + "Marinara", + "Quattro Formaggi", + "Prosciutto e Funghi" + ], + "openNow": true + } + ], + "filters": {}, + "totalShops": 2 + } + } + ], + "tags": [ + "list", + "filter", + "sort" + ] + }, + { + "uri": "/pizza-shop", + "name": "Pizza Shop Details", + "description": "Detailed information about a specific pizza shop", + "examples": [ + { + "name": "Shop Detail", + "description": "Show details for a specific shop", + "data": { + "shop": { + "id": "tonys-pizza", + "name": "Tony's New York Pizza", + "description": "Authentic New York-style pizza with a crispy thin crust", + "address": "1570 Stockton St, San Francisco, CA 94133", + "coords": [ + -122.4106, + 37.8006 + ], + "rating": 4.5, + "reviews": 1250, + "priceLevel": 2, + "cuisine": [ + "Italian", + "Pizza", + "New York Style" + ], + "hours": { + "open": "11:00 AM", + "close": "10:00 PM" + }, + "phone": "(415) 835-9888", + "website": "https://tonyspizzasf.com", + "image": "https://images.unsplash.com/photo-1513104890138-7c749659a591", + "specialties": [ + "Margherita", + "Pepperoni", + "White Pizza" + ], + "openNow": true + }, + "relatedShops": [ + { + "id": "bella-napoli", + "name": "Bella Napoli", + "description": "Traditional Neapolitan pizza baked in a wood-fired oven", + "address": "3854 Geary Blvd, San Francisco, CA 94118", + "coords": [ + -122.4603, + 37.7808 + ], + "rating": 4.7, + "reviews": 890, + "priceLevel": 3, + "cuisine": [ + "Italian", + "Pizza", + "Neapolitan" + ], + "hours": { + "open": "12:00 PM", + "close": "9:00 PM" + }, + "phone": "(415) 221-0305", + "image": "https://images.unsplash.com/photo-1574071318508-1cdbab80d002", + "specialties": [ + "Marinara", + "Quattro Formaggi", + "Prosciutto e Funghi" + ], + "openNow": true + } + ] + } + } + ], + "tags": [ + "detail", + "info", + "contact" + ] + } + ], + "generatedAt": "2025-12-03T00:00:00.000Z" +} \ No newline at end of file diff --git a/sample-apps/Triage/tsconfig.json b/sample-apps/Triage/tsconfig.json new file mode 100644 index 000000000..6df25d8a8 --- /dev/null +++ b/sample-apps/Triage/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "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"] +} \ No newline at end of file