From a60ee35a068305247a4b6a6db12677c11dc7bea8 Mon Sep 17 00:00:00 2001 From: Raiyan kammal Date: Sat, 1 Aug 2026 14:19:31 +0530 Subject: [PATCH] added sample project alphatext-mcp --- .../.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 ++ .../.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 ++ .../.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 ++ .../.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 ++ .../.cursor/skills/ui-widgets/SKILL.md | 251 + sample-apps/alphatex-mcp/.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 ++ .../.gemini/skills/ui-widgets/SKILL.md | 251 + sample-apps/alphatex-mcp/.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/alphatex-mcp/README.md | 54 + sample-apps/alphatex-mcp/package-lock.json | 4563 +++++++++++++++++ sample-apps/alphatex-mcp/package.json | 39 + sample-apps/alphatex-mcp/src/app.module.ts | 28 + sample-apps/alphatex-mcp/src/index.ts | 24 + .../alphatex-mcp/src/lib/erp-engine.test.ts | 71 + .../alphatex-mcp/src/lib/erp-engine.ts | 274 + sample-apps/alphatex-mcp/src/lib/erp-store.ts | 332 ++ .../src/lib/invoice-html-template.ts | 301 ++ .../src/modules/invoice/invoice.module.ts | 21 + .../src/modules/pizzaz/pizzaz.data.ts | 106 + .../src/modules/pizzaz/pizzaz.module.ts | 12 + .../src/modules/pizzaz/pizzaz.service.ts | 60 + .../src/modules/pizzaz/pizzaz.tasks.ts | 294 ++ .../src/modules/pizzaz/pizzaz.tools.ts | 212 + .../alphatex-mcp/src/tools/banking.tools.ts | 79 + .../alphatex-mcp/src/tools/invoice.tools.ts | 203 + .../alphatex-mcp/src/tools/item.tools.ts | 104 + .../alphatex-mcp/src/tools/note.tools.ts | 64 + .../alphatex-mcp/src/tools/party.tools.ts | 87 + .../alphatex-mcp/src/tools/payment.tools.ts | 74 + .../alphatex-mcp/src/widgets/app/globals.css | 10 + .../src/widgets/app/invoice-viewer/page.tsx | 9 + .../alphatex-mcp/src/widgets/app/layout.tsx | 19 + .../src/widgets/app/pizza-list/page.tsx | 272 + .../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 + .../src/widgets/invoice-viewer.tsx | 604 +++ .../alphatex-mcp/src/widgets/next-env.d.ts | 5 + .../alphatex-mcp/src/widgets/next.config.js | 45 + .../src/widgets/package-lock.json | 2974 +++++++++++ .../alphatex-mcp/src/widgets/package.json | 31 + .../alphatex-mcp/src/widgets/tsconfig.json | 28 + .../src/widgets/widget-manifest.json | 94 + sample-apps/alphatex-mcp/tsconfig.json | 23 + 79 files changed, 20840 insertions(+) create mode 100644 sample-apps/alphatex-mcp/.agents/skills/auth-security/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.agents/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.agents/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.agents/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.agents/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.antigravity/skills/auth-security/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.antigravity/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.antigravity/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.antigravity/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.antigravity/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.claude/skills/auth-security/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.claude/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.claude/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.claude/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.claude/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.codex/skills/auth-security/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.codex/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.codex/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.codex/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.codex/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.copilot/skills/auth-security/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.copilot/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.copilot/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.copilot/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.copilot/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.cursor/skills/auth-security/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.cursor/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.cursor/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.cursor/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.cursor/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.env.example create mode 100644 sample-apps/alphatex-mcp/.gemini/skills/auth-security/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.gemini/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.gemini/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.gemini/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.gemini/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.gitignore create mode 100644 sample-apps/alphatex-mcp/.opencode/skills/auth-security/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.opencode/skills/mcp-app-architecture/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.opencode/skills/middleware-pipeline/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.opencode/skills/tools-resources-prompts/SKILL.md create mode 100644 sample-apps/alphatex-mcp/.opencode/skills/ui-widgets/SKILL.md create mode 100644 sample-apps/alphatex-mcp/README.md create mode 100644 sample-apps/alphatex-mcp/package-lock.json create mode 100644 sample-apps/alphatex-mcp/package.json create mode 100644 sample-apps/alphatex-mcp/src/app.module.ts create mode 100644 sample-apps/alphatex-mcp/src/index.ts create mode 100644 sample-apps/alphatex-mcp/src/lib/erp-engine.test.ts create mode 100644 sample-apps/alphatex-mcp/src/lib/erp-engine.ts create mode 100644 sample-apps/alphatex-mcp/src/lib/erp-store.ts create mode 100644 sample-apps/alphatex-mcp/src/lib/invoice-html-template.ts create mode 100644 sample-apps/alphatex-mcp/src/modules/invoice/invoice.module.ts create mode 100644 sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.data.ts create mode 100644 sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.module.ts create mode 100644 sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.service.ts create mode 100644 sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.tasks.ts create mode 100644 sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.tools.ts create mode 100644 sample-apps/alphatex-mcp/src/tools/banking.tools.ts create mode 100644 sample-apps/alphatex-mcp/src/tools/invoice.tools.ts create mode 100644 sample-apps/alphatex-mcp/src/tools/item.tools.ts create mode 100644 sample-apps/alphatex-mcp/src/tools/note.tools.ts create mode 100644 sample-apps/alphatex-mcp/src/tools/party.tools.ts create mode 100644 sample-apps/alphatex-mcp/src/tools/payment.tools.ts create mode 100644 sample-apps/alphatex-mcp/src/widgets/app/globals.css create mode 100644 sample-apps/alphatex-mcp/src/widgets/app/invoice-viewer/page.tsx create mode 100644 sample-apps/alphatex-mcp/src/widgets/app/layout.tsx create mode 100644 sample-apps/alphatex-mcp/src/widgets/app/pizza-list/page.tsx create mode 100644 sample-apps/alphatex-mcp/src/widgets/app/pizza-map/page.tsx create mode 100644 sample-apps/alphatex-mcp/src/widgets/app/pizza-shop/page.tsx create mode 100644 sample-apps/alphatex-mcp/src/widgets/components/CompactShopCard.tsx create mode 100644 sample-apps/alphatex-mcp/src/widgets/components/PizzaCard.tsx create mode 100644 sample-apps/alphatex-mcp/src/widgets/invoice-viewer.tsx create mode 100644 sample-apps/alphatex-mcp/src/widgets/next-env.d.ts create mode 100644 sample-apps/alphatex-mcp/src/widgets/next.config.js create mode 100644 sample-apps/alphatex-mcp/src/widgets/package-lock.json create mode 100644 sample-apps/alphatex-mcp/src/widgets/package.json create mode 100644 sample-apps/alphatex-mcp/src/widgets/tsconfig.json create mode 100644 sample-apps/alphatex-mcp/src/widgets/widget-manifest.json create mode 100644 sample-apps/alphatex-mcp/tsconfig.json diff --git a/sample-apps/alphatex-mcp/.agents/skills/auth-security/SKILL.md b/sample-apps/alphatex-mcp/.agents/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.agents/skills/mcp-app-architecture/SKILL.md b/sample-apps/alphatex-mcp/.agents/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.agents/skills/middleware-pipeline/SKILL.md b/sample-apps/alphatex-mcp/.agents/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.agents/skills/tools-resources-prompts/SKILL.md b/sample-apps/alphatex-mcp/.agents/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.agents/skills/ui-widgets/SKILL.md b/sample-apps/alphatex-mcp/.agents/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.antigravity/skills/auth-security/SKILL.md b/sample-apps/alphatex-mcp/.antigravity/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.antigravity/skills/mcp-app-architecture/SKILL.md b/sample-apps/alphatex-mcp/.antigravity/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.antigravity/skills/middleware-pipeline/SKILL.md b/sample-apps/alphatex-mcp/.antigravity/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.antigravity/skills/tools-resources-prompts/SKILL.md b/sample-apps/alphatex-mcp/.antigravity/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.antigravity/skills/ui-widgets/SKILL.md b/sample-apps/alphatex-mcp/.antigravity/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.claude/skills/auth-security/SKILL.md b/sample-apps/alphatex-mcp/.claude/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.claude/skills/mcp-app-architecture/SKILL.md b/sample-apps/alphatex-mcp/.claude/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.claude/skills/middleware-pipeline/SKILL.md b/sample-apps/alphatex-mcp/.claude/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.claude/skills/tools-resources-prompts/SKILL.md b/sample-apps/alphatex-mcp/.claude/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.claude/skills/ui-widgets/SKILL.md b/sample-apps/alphatex-mcp/.claude/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.codex/skills/auth-security/SKILL.md b/sample-apps/alphatex-mcp/.codex/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.codex/skills/mcp-app-architecture/SKILL.md b/sample-apps/alphatex-mcp/.codex/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.codex/skills/middleware-pipeline/SKILL.md b/sample-apps/alphatex-mcp/.codex/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.codex/skills/tools-resources-prompts/SKILL.md b/sample-apps/alphatex-mcp/.codex/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.codex/skills/ui-widgets/SKILL.md b/sample-apps/alphatex-mcp/.codex/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.copilot/skills/auth-security/SKILL.md b/sample-apps/alphatex-mcp/.copilot/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.copilot/skills/mcp-app-architecture/SKILL.md b/sample-apps/alphatex-mcp/.copilot/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.copilot/skills/middleware-pipeline/SKILL.md b/sample-apps/alphatex-mcp/.copilot/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.copilot/skills/tools-resources-prompts/SKILL.md b/sample-apps/alphatex-mcp/.copilot/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.copilot/skills/ui-widgets/SKILL.md b/sample-apps/alphatex-mcp/.copilot/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.cursor/skills/auth-security/SKILL.md b/sample-apps/alphatex-mcp/.cursor/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.cursor/skills/mcp-app-architecture/SKILL.md b/sample-apps/alphatex-mcp/.cursor/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.cursor/skills/middleware-pipeline/SKILL.md b/sample-apps/alphatex-mcp/.cursor/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.cursor/skills/tools-resources-prompts/SKILL.md b/sample-apps/alphatex-mcp/.cursor/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.cursor/skills/ui-widgets/SKILL.md b/sample-apps/alphatex-mcp/.cursor/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.env.example b/sample-apps/alphatex-mcp/.env.example new file mode 100644 index 000000000..0c3f65fa5 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.gemini/skills/auth-security/SKILL.md b/sample-apps/alphatex-mcp/.gemini/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.gemini/skills/mcp-app-architecture/SKILL.md b/sample-apps/alphatex-mcp/.gemini/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.gemini/skills/middleware-pipeline/SKILL.md b/sample-apps/alphatex-mcp/.gemini/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.gemini/skills/tools-resources-prompts/SKILL.md b/sample-apps/alphatex-mcp/.gemini/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.gemini/skills/ui-widgets/SKILL.md b/sample-apps/alphatex-mcp/.gemini/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.gitignore b/sample-apps/alphatex-mcp/.gitignore new file mode 100644 index 000000000..b5b5532bc --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.opencode/skills/auth-security/SKILL.md b/sample-apps/alphatex-mcp/.opencode/skills/auth-security/SKILL.md new file mode 100644 index 000000000..17256a3e2 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.opencode/skills/mcp-app-architecture/SKILL.md b/sample-apps/alphatex-mcp/.opencode/skills/mcp-app-architecture/SKILL.md new file mode 100644 index 000000000..fda31bdee --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.opencode/skills/middleware-pipeline/SKILL.md b/sample-apps/alphatex-mcp/.opencode/skills/middleware-pipeline/SKILL.md new file mode 100644 index 000000000..dabe295ac --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.opencode/skills/tools-resources-prompts/SKILL.md b/sample-apps/alphatex-mcp/.opencode/skills/tools-resources-prompts/SKILL.md new file mode 100644 index 000000000..bf34b732d --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/.opencode/skills/ui-widgets/SKILL.md b/sample-apps/alphatex-mcp/.opencode/skills/ui-widgets/SKILL.md new file mode 100644 index 000000000..55b7af712 --- /dev/null +++ b/sample-apps/alphatex-mcp/.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/alphatex-mcp/README.md b/sample-apps/alphatex-mcp/README.md new file mode 100644 index 000000000..1a533fbd4 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/package-lock.json b/sample-apps/alphatex-mcp/package-lock.json new file mode 100644 index 000000000..e24bfd35f --- /dev/null +++ b/sample-apps/alphatex-mcp/package-lock.json @@ -0,0 +1,4563 @@ +{ + "name": "temp-nitro", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "temp-nitro", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "@nitrostack/core": "^1", + "dotenv": "^16.3.1", + "zod": "^3.22.4" + }, + "devDependencies": { + "@nitrostack/cli": "^1", + "@types/node": "^22.10.0", + "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.10.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.10.0.tgz", + "integrity": "sha512-Xk3JQ+cdychsvftrV3G9ZrN9W329lbyFW0pGJXFGKFQf8qr4upw2SgNg9BVorjSrfhoXZRnJGt/uNF4nGFBL5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6" + } + }, + "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/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/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansi-styles/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ansi-styles/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", + "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.6.tgz", + "integrity": "sha512-iQxPClE07hETVpbRoX7JXX3v/ZQViCxe/SYCxylRLzdEx1xJAufPptfiOqR8tqiCtmbtMDANKWszzjLu1PMAZQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz", + "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.33", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.33.tgz", + "integrity": "sha512-+SwvkaiJtxsiPjhy9LivY/1m7UsNqCJetM1BrZl9A5DkQhlbHQDU730mMiDPWjnoCYOM8Chf3WrCJw27kNTPFQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "9.3.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.8.tgz", + "integrity": "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.2", + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jose": { + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.6.tgz", + "integrity": "sha512-HwMtbJjMw8rC8dUTwCNilHJD+fxTeKM3JV1eprSmTjS41qwXSSt6exJXgyPK1QOu0jB9eDYLESRDkB3qaT3jnw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/posthog-node": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.21.2.tgz", + "integrity": "sha512-Jehlu0KguL1LLyUczCt86OtA5INmeStK3zcgbv1BSyMcNxs0HP3GQogBrYhwhqHsk6JopiFFVpJyZEoXOUMhGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@posthog/core": "1.10.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/sample-apps/alphatex-mcp/package.json b/sample-apps/alphatex-mcp/package.json new file mode 100644 index 000000000..ba4a3c7e5 --- /dev/null +++ b/sample-apps/alphatex-mcp/package.json @@ -0,0 +1,39 @@ +{ + "name": "temp-nitro", + "version": "1.0.0", + "private": true, + "type": "module", + "description": "AlphaTex Invoicer 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": "AI", + "license": "MIT", + "dependencies": { + "@nitrostack/core": "^1", + "zod": "^3.22.4", + "dotenv": "^16.3.1", + "@modelcontextprotocol/ext-apps": ">=0.1.0" + }, + "devDependencies": { + "@nitrostack/cli": "^1", + "@types/node": "^22.10.0", + "typescript": "^5.3.3" + }, + "nitrostack": { + "skillsVersion": "1.0.0" + } +} diff --git a/sample-apps/alphatex-mcp/src/app.module.ts b/sample-apps/alphatex-mcp/src/app.module.ts new file mode 100644 index 000000000..0ddfacb13 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/app.module.ts @@ -0,0 +1,28 @@ +import { McpApp, Module, ConfigModule } from '@nitrostack/core'; +import { InvoiceModule } from './modules/invoice/invoice.module.js'; + +/** + * Root Application Module + * + * AlphaTex Invoicer MCP Server. + * Registers core business logic, tax calculation engine, invoice tools and UI widgets. + */ +@McpApp({ + module: AppModule, + server: { + name: 'alphatex-invoicer', + version: '1.0.0' + }, + logging: { + level: 'info' + } +}) +@Module({ + name: 'alphatex', + description: 'AlphaTex Invoicer MCP Server Root Module', + imports: [ + ConfigModule.forRoot(), + InvoiceModule + ], +}) +export class AppModule { } diff --git a/sample-apps/alphatex-mcp/src/index.ts b/sample-apps/alphatex-mcp/src/index.ts new file mode 100644 index 000000000..f21f7c79d --- /dev/null +++ b/sample-apps/alphatex-mcp/src/index.ts @@ -0,0 +1,24 @@ +/** + * AlphaTex Invoicer MCP Server + * + * GST Tax Invoice calculation, line item pricing, and interactive UI widget rendering. + */ + +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/alphatex-mcp/src/lib/erp-engine.test.ts b/sample-apps/alphatex-mcp/src/lib/erp-engine.test.ts new file mode 100644 index 000000000..d750b34b7 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/lib/erp-engine.test.ts @@ -0,0 +1,71 @@ +import { calculateInvoiceTotals, calculateLineItem, numberToWordsINR, round2 } from './erp-engine.js'; + +function runTests() { + console.log('--- Testing AlphaTex ERP Engine ---'); + + // Test 1: Rounding + console.assert(round2(100.456) === 100.46, 'Round 100.456 should be 100.46'); + console.assert(round2(100.444) === 100.44, 'Round 100.444 should be 100.44'); + + // Test 2: Amount to Words INR + const words = numberToWordsINR(150450); + console.assert( + words === 'Rupees One Lakh Fifty Thousand Four Hundred and Fifty Only', + `Expected 'Rupees One Lakh Fifty Thousand Four Hundred and Fifty Only', got '${words}'` + ); + + // Test 3: Intrastate line item (5% tax rate -> 2.5% CGST, 2.5% SGST) + const line1 = calculateLineItem({ + description: 'Cotton Yarn', + qty: 10, + rate: 100, + discountPct: 0, + taxRatePct: 5, + isInterstate: false, + }); + + console.assert(line1.grossAmount === 1000, 'Gross amount should be 1000'); + console.assert(line1.taxableValue === 1000, 'Taxable value should be 1000'); + console.assert(line1.cgstAmount === 25, 'CGST should be 25'); + console.assert(line1.sgstAmount === 25, 'SGST should be 25'); + console.assert(line1.igstAmount === 0, 'IGST should be 0'); + console.assert(line1.lineTotal === 1050, 'Line total should be 1050'); + + // Test 4: Interstate line item (18% tax rate -> 18% IGST) + const line2 = calculateLineItem({ + description: 'Dyeing Chemical', + qty: 50, + rate: 200, + discountPct: 10, + taxRatePct: 18, + isInterstate: true, + }); + + console.assert(line2.grossAmount === 10000, 'Gross amount 10000'); + console.assert(line2.discountAmount === 1000, 'Discount amount 1000'); + console.assert(line2.taxableValue === 9000, 'Taxable value 9000'); + console.assert(line2.igstAmount === 1620, 'IGST amount 1620'); + console.assert(line2.cgstAmount === 0 && line2.sgstAmount === 0, 'CGST/SGST 0 for interstate'); + + // Test 5: Full Invoice Totals calculation + const invoiceTotals = calculateInvoiceTotals( + [ + { qty: 100, rate: 50, discountPct: 0, taxRatePct: 12, hsn: '5205' }, + { qty: 50, rate: 100, discountPct: 10, taxRatePct: 18, hsn: '5205' }, + ], + { + isInterstate: false, + freightCharges: 200, + autoRound: true, + } + ); + + console.assert(invoiceTotals.subtotalGross === 10000, 'Subtotal gross 10000'); + console.assert(invoiceTotals.totalItemDiscount === 500, 'Item discount 500'); + console.assert(invoiceTotals.netTaxableValue === 9700, 'Net taxable value 9700 (9500 + 200 freight)'); + console.assert(invoiceTotals.grandTotal > 0, 'Grand total should be computed'); + + console.log('✅ ALL ERP ENGINE TESTS PASSED SUCCESSFULLY!'); +} + +runTests(); diff --git a/sample-apps/alphatex-mcp/src/lib/erp-engine.ts b/sample-apps/alphatex-mcp/src/lib/erp-engine.ts new file mode 100644 index 000000000..f00288184 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/lib/erp-engine.ts @@ -0,0 +1,274 @@ +/** + * AlphaTex ERP - Core Calculation & Currency Engine (erp-engine.ts) + * Guarantees exact paisa-level rounding, GST splits, discounts, additional charges, + * currency formatting, and Indian Rupees amount-in-words conversion. + */ + +export interface LineItemInput { + description?: string; + hsn?: string; + qty: number; + rate: number; + discountPct?: number; + taxRatePct?: number; + isInterstate?: boolean; +} + +export interface CalculatedLineItem { + description: string; + hsn: string; + qty: number; + rate: number; + grossAmount: number; + discountPct: number; + discountAmount: number; + taxableValue: number; + taxRatePct: number; + cgstRate: number; + cgstAmount: number; + sgstRate: number; + sgstAmount: number; + igstRate: number; + igstAmount: number; + totalTax: number; + lineTotal: number; +} + +export interface HsnSummaryItem { + hsn: string; + taxableValue: number; + taxRatePct: number; + cgstAmount: number; + sgstAmount: number; + igstAmount: number; + totalTax: number; +} + +export interface InvoiceExtrasInput { + invoiceDiscountPct?: number; + freightCharges?: number; + packingCharges?: number; + isInterstate?: boolean; + autoRound?: boolean; +} + +export interface InvoiceTotals { + items: CalculatedLineItem[]; + hsnSummary: HsnSummaryItem[]; + subtotalGross: number; + totalItemDiscount: number; + totalTaxableValue: number; + invoiceDiscountPct: number; + invoiceDiscountAmount: number; + freightCharges: number; + packingCharges: number; + netTaxableValue: number; + totalCGST: number; + totalSGST: number; + totalIGST: number; + totalTaxAmount: number; + unroundedGrandTotal: number; + roundOff: number; + grandTotal: number; + amountInWords: string; +} + +/** + * Helper to round float to 2 decimal places reliably + */ +export function round2(num: number): number { + return Math.round((Number(num) + Number.EPSILON) * 100) / 100; +} + +/** + * Format number as currency (e.g. ₹ 1,50,450.00) + */ +export function formatCurrency(amount: number, currencySymbol = '₹'): string { + const val = round2(amount || 0); + const formatted = val.toLocaleString('en-IN', { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + return `${currencySymbol} ${formatted}`; +} + +/** + * Convert number to Indian Rupees Amount in Words (e.g. 150450 -> "Rupees One Lakh Fifty Thousand Four Hundred Fifty Only") + */ +export function numberToWordsINR(amount: number): string { + const val = round2(amount); + if (isNaN(val) || val < 0) return 'Rupees Zero Only'; + + const a = [ + '', 'One ', 'Two ', 'Three ', 'Four ', 'Five ', 'Six ', 'Seven ', 'Eight ', 'Nine ', 'Ten ', + 'Eleven ', 'Twelve ', 'Thirteen ', 'Fourteen ', 'Fifteen ', 'Sixteen ', 'Seventeen ', 'Eighteen ', 'Nineteen ' + ]; + const b = ['', '', 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety']; + + function inWords(num: number): string { + const numStr = num.toString(); + if (numStr.length > 9) return 'overflow'; + const n = ('000000000' + numStr).substr(-9); + const match = n.match(/^(\d{2})(\d{2})(\d{2})(\d{1})(\d{2})$/); + if (!match) return ''; + let str = ''; + str += (Number(match[1]) !== 0) ? (a[Number(match[1])] || b[Number(match[1][0])] + ' ' + a[Number(match[1][1])]) + 'Crore ' : ''; + str += (Number(match[2]) !== 0) ? (a[Number(match[2])] || b[Number(match[2][0])] + ' ' + a[Number(match[2][1])]) + 'Lakh ' : ''; + str += (Number(match[3]) !== 0) ? (a[Number(match[3])] || b[Number(match[3][0])] + ' ' + a[Number(match[3][1])]) + 'Thousand ' : ''; + str += (Number(match[4]) !== 0) ? (a[Number(match[4])] || b[Number(match[4][0])] + ' ' + a[Number(match[4][1])]) + 'Hundred ' : ''; + str += (Number(match[5]) !== 0) ? ((str !== '') ? 'and ' : '') + (a[Number(match[5])] || b[Number(match[5][0])] + ' ' + a[Number(match[5][1])]) : ''; + return str; + } + + const integerPart = Math.floor(val); + const paisePart = Math.round((val - integerPart) * 100); + + let words = 'Rupees ' + (integerPart === 0 ? 'Zero ' : inWords(integerPart)); + if (paisePart > 0) { + words += 'and ' + inWords(paisePart) + 'Paise '; + } + words += 'Only'; + return words.replace(/\s+/g, ' ').trim(); +} + +/** + * Calculate single line-item taxable value and tax details + */ +export function calculateLineItem(item: LineItemInput): CalculatedLineItem { + const qty = round2(item.qty || 0); + const rate = round2(item.rate || 0); + const discountPct = round2(item.discountPct || 0); + const taxRatePct = round2(item.taxRatePct || 0); + const isInterstate = Boolean(item.isInterstate); + const description = item.description || 'Item'; + const hsn = item.hsn || 'GENERAL'; + + const grossAmount = round2(qty * rate); + const discountAmount = round2((grossAmount * discountPct) / 100); + const taxableValue = round2(grossAmount - discountAmount); + + let cgstRate = 0, sgstRate = 0, igstRate = 0; + let cgstAmount = 0, sgstAmount = 0, igstAmount = 0; + + if (isInterstate) { + igstRate = taxRatePct; + igstAmount = round2((taxableValue * igstRate) / 100); + } else { + cgstRate = round2(taxRatePct / 2); + sgstRate = cgstRate; + cgstAmount = round2((taxableValue * cgstRate) / 100); + sgstAmount = cgstAmount; // Exact half split + } + + const totalTax = round2(cgstAmount + sgstAmount + igstAmount); + const lineTotal = round2(taxableValue + totalTax); + + return { + description, + hsn, + qty, + rate, + grossAmount, + discountPct, + discountAmount, + taxableValue, + taxRatePct, + cgstRate, + cgstAmount, + sgstRate, + sgstAmount, + igstRate, + igstAmount, + totalTax, + lineTotal + }; +} + +/** + * Calculate total invoice summary, HSN breakdown, additional charges, and round-off + */ +export function calculateInvoiceTotals(items: LineItemInput[] = [], extras: InvoiceExtrasInput = {}): InvoiceTotals { + let subtotalGross = 0; + let totalItemDiscount = 0; + let totalTaxableValue = 0; + let totalCGST = 0; + let totalSGST = 0; + let totalIGST = 0; + const hsnSummaryMap: Record = {}; + + const calculatedItems = items.map(rawItem => { + const calculated = calculateLineItem({ + ...rawItem, + isInterstate: extras.isInterstate + }); + + subtotalGross = round2(subtotalGross + calculated.grossAmount); + totalItemDiscount = round2(totalItemDiscount + calculated.discountAmount); + totalTaxableValue = round2(totalTaxableValue + calculated.taxableValue); + totalCGST = round2(totalCGST + calculated.cgstAmount); + totalSGST = round2(totalSGST + calculated.sgstAmount); + totalIGST = round2(totalIGST + calculated.igstAmount); + + // Group by HSN + const hsn = rawItem.hsn || 'GENERAL'; + if (!hsnSummaryMap[hsn]) { + hsnSummaryMap[hsn] = { + hsn, + taxableValue: 0, + taxRatePct: calculated.taxRatePct, + cgstAmount: 0, + sgstAmount: 0, + igstAmount: 0, + totalTax: 0 + }; + } + hsnSummaryMap[hsn].taxableValue = round2(hsnSummaryMap[hsn].taxableValue + calculated.taxableValue); + hsnSummaryMap[hsn].cgstAmount = round2(hsnSummaryMap[hsn].cgstAmount + calculated.cgstAmount); + hsnSummaryMap[hsn].sgstAmount = round2(hsnSummaryMap[hsn].sgstAmount + calculated.sgstAmount); + hsnSummaryMap[hsn].igstAmount = round2(hsnSummaryMap[hsn].igstAmount + calculated.igstAmount); + hsnSummaryMap[hsn].totalTax = round2(hsnSummaryMap[hsn].totalTax + calculated.totalTax); + + return calculated; + }); + + // Invoice Level Discounts & Additional Charges + const invoiceDiscountPct = round2(extras.invoiceDiscountPct || 0); + const invoiceDiscountAmount = round2((totalTaxableValue * invoiceDiscountPct) / 100); + const freightCharges = round2(extras.freightCharges || 0); + const packingCharges = round2(extras.packingCharges || 0); + + const netTaxableValue = round2(totalTaxableValue - invoiceDiscountAmount + freightCharges + packingCharges); + const totalTaxAmount = round2(totalCGST + totalSGST + totalIGST); + + const unroundedGrandTotal = round2(netTaxableValue + totalTaxAmount); + let grandTotal = unroundedGrandTotal; + let roundOff = 0; + + if (extras.autoRound !== false) { + grandTotal = Math.round(unroundedGrandTotal); + roundOff = round2(grandTotal - unroundedGrandTotal); + } + + const amountInWords = numberToWordsINR(grandTotal); + + return { + items: calculatedItems, + hsnSummary: Object.values(hsnSummaryMap), + subtotalGross, + totalItemDiscount, + totalTaxableValue, + invoiceDiscountPct, + invoiceDiscountAmount, + freightCharges, + packingCharges, + netTaxableValue, + totalCGST, + totalSGST, + totalIGST, + totalTaxAmount, + unroundedGrandTotal, + roundOff, + grandTotal, + amountInWords + }; +} diff --git a/sample-apps/alphatex-mcp/src/lib/erp-store.ts b/sample-apps/alphatex-mcp/src/lib/erp-store.ts new file mode 100644 index 000000000..2ecb2f7a6 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/lib/erp-store.ts @@ -0,0 +1,332 @@ +/** + * AlphaTex ERP - Central Unified Data Store (erp-store.ts) + * Maintains in-memory state with sample data for Items, Parties, Invoices, + * Credit/Debit Notes, Payments, Ledger Entries, and Bank Accounts. + */ + +export interface ItemRecord { + id: string; + name: string; + code: string; + hsn: string; + unit: string; + rate: number; + taxRatePct: number; + currentStock: number; + minStockWarning: number; +} + +export interface PartyRecord { + id: string; + name: string; + gstin: string; + phone: string; + email: string; + address: string; + state: string; + stateCode: string; + openingBalance: number; + balanceType: 'Receivable' | 'Payable'; + currentBalance: number; +} + +export interface PaymentRecord { + id: string; + partyName: string; + amount: number; + date: string; + type: 'RECEIVED' | 'PAID'; + paymentMode: 'CASH' | 'CHEQUE' | 'UPI' | 'NEFT'; + referenceNo: string; + notes?: string; +} + +export interface NoteRecord { + id: string; + noteNo: string; + type: 'CREDIT' | 'DEBIT'; + partyName: string; + date: string; + reason: string; + amount: number; + taxRatePct: number; + totalAmount: number; +} + +export interface BankAccountRecord { + id: string; + bankName: string; + accountNo: string; + ifsc: string; + branch: string; + accountType: string; +} + +export interface ChequeRecord { + id: string; + chequeNo: string; + date: string; + partyName: string; + amount: number; + bankName: string; + status: 'PENDING' | 'CLEARED' | 'BOUNCED'; + type: 'ISSUED' | 'RECEIVED'; +} + +class ERPStore { + private items: Map = new Map(); + private parties: Map = new Map(); + private payments: PaymentRecord[] = []; + private notes: NoteRecord[] = []; + private bankAccounts: BankAccountRecord[] = []; + private cheques: ChequeRecord[] = []; + + constructor() { + this.seedInitialData(); + } + + private seedInitialData() { + // Seed Items + const sampleItems: ItemRecord[] = [ + { + id: 'ITM-001', + name: '100% Combed Cotton Yarn 30s', + code: 'YRN-30S', + hsn: '5205', + unit: 'Kgs', + rate: 280, + taxRatePct: 5, + currentStock: 1250, + minStockWarning: 200, + }, + { + id: 'ITM-002', + name: 'Carded Cotton Yarn 40s', + code: 'YRN-40S', + hsn: '5205', + unit: 'Kgs', + rate: 310, + taxRatePct: 5, + currentStock: 850, + minStockWarning: 150, + }, + { + id: 'ITM-003', + name: 'Bio-Wash Hosiery Fabric (Navy Blue)', + code: 'FAB-NAVY', + hsn: '6006', + unit: 'Meters', + rate: 450, + taxRatePct: 12, + currentStock: 500, + minStockWarning: 100, + }, + { + id: 'ITM-004', + name: 'Reactive Textile Dye (Royal Blue)', + code: 'DYE-RBL', + hsn: '3204', + unit: 'Kgs', + rate: 620, + taxRatePct: 18, + currentStock: 120, + minStockWarning: 30, + }, + ]; + sampleItems.forEach(i => this.items.set(i.id, i)); + + // Seed Parties + const sampleParties: PartyRecord[] = [ + { + id: 'PRT-001', + name: 'Apex Fabrics & Garments Ltd.', + gstin: '33BBBBB1111B2Z2', + phone: '+91 91234 56789', + email: 'purchase@apexfabrics.com', + address: '45 Fashion Highway, Erode, Tamil Nadu - 638001', + state: 'Tamil Nadu', + stateCode: '33', + openingBalance: 50000, + balanceType: 'Receivable', + currentBalance: 191650, + }, + { + id: 'PRT-002', + name: 'Vardhaman Spinning Mills', + gstin: '27AAAAA9999A1Z1', + phone: '+91 98220 11223', + email: 'sales@vardhamanmills.com', + address: 'Industrial Estate, Ludhiana, Punjab - 141003', + state: 'Punjab', + stateCode: '03', + openingBalance: 120000, + balanceType: 'Payable', + currentBalance: 120000, + }, + { + id: 'PRT-003', + name: 'Sri Krishna Dyers & Printers', + gstin: '33CCCC3333C3Z3', + phone: '+91 94433 88776', + email: 'info@krishnadyers.in', + address: '88SIPCOT Industrial Complex, Perundurai, Tamil Nadu - 638052', + state: 'Tamil Nadu', + stateCode: '33', + openingBalance: 0, + balanceType: 'Receivable', + currentBalance: 35000, + }, + ]; + sampleParties.forEach(p => this.parties.set(p.id, p)); + + // Seed Bank Accounts + this.bankAccounts.push({ + id: 'BNK-001', + bankName: 'State Bank of India', + accountNo: '39847192834', + ifsc: 'SBIN0001234', + branch: 'Main Branch, Tirupur', + accountType: 'Current Account', + }); + + // Seed Payments + this.payments.push({ + id: 'PAY-001', + partyName: 'Apex Fabrics & Garments Ltd.', + amount: 50000, + date: '2026-07-25', + type: 'RECEIVED', + paymentMode: 'NEFT', + referenceNo: 'N29384729102', + notes: 'Advance against upcoming yarn order', + }); + + // Seed Cheques + this.cheques.push({ + id: 'CHQ-001', + chequeNo: '004921', + date: '2026-08-05', + partyName: 'Vardhaman Spinning Mills', + amount: 60000, + bankName: 'State Bank of India', + status: 'PENDING', + type: 'ISSUED', + }); + } + + // --- ITEM METHODS --- + addItem(item: Omit): ItemRecord { + const id = `ITM-${String(this.items.size + 1).padStart(3, '0')}`; + const newRecord: ItemRecord = { id, ...item }; + this.items.set(id, newRecord); + return newRecord; + } + + getItems(): ItemRecord[] { + return Array.from(this.items.values()); + } + + getItemByQuery(query: string): ItemRecord | undefined { + const q = query.toLowerCase(); + return this.getItems().find( + i => i.name.toLowerCase().includes(q) || i.code.toLowerCase() === q || i.hsn === q + ); + } + + updateStock(id: string, deltaQty: number): ItemRecord | undefined { + const item = this.items.get(id); + if (!item) return undefined; + item.currentStock += deltaQty; + return item; + } + + // --- PARTY METHODS --- + addParty(party: Omit): PartyRecord { + const id = `PRT-${String(this.parties.size + 1).padStart(3, '0')}`; + const newRecord: PartyRecord = { + id, + ...party, + currentBalance: party.openingBalance, + }; + this.parties.set(id, newRecord); + return newRecord; + } + + getParties(): PartyRecord[] { + return Array.from(this.parties.values()); + } + + getPartyByName(name: string): PartyRecord | undefined { + const n = name.toLowerCase(); + return this.getParties().find(p => p.name.toLowerCase().includes(n)); + } + + updatePartyBalance(partyName: string, deltaAmount: number) { + const party = this.getPartyByName(partyName); + if (party) { + party.currentBalance += deltaAmount; + } + } + + // --- PAYMENT METHODS --- + recordPayment(pay: Omit): PaymentRecord { + const id = `PAY-${String(this.payments.length + 1).padStart(3, '0')}`; + const record: PaymentRecord = { id, ...pay }; + this.payments.push(record); + + // Adjust balance: RECEIVED reduces receivable balance + const delta = pay.type === 'RECEIVED' ? -pay.amount : pay.amount; + this.updatePartyBalance(pay.partyName, delta); + + return record; + } + + getPayments(partyName?: string): PaymentRecord[] { + if (!partyName) return this.payments; + const p = partyName.toLowerCase(); + return this.payments.filter(pay => pay.partyName.toLowerCase().includes(p)); + } + + // --- CREDIT / DEBIT NOTE METHODS --- + addNote(note: Omit): NoteRecord { + const id = `NTE-${String(this.notes.length + 1).padStart(3, '0')}`; + const record: NoteRecord = { id, ...note }; + this.notes.push(record); + + // Credit Note reduces receivable balance, Debit Note increases it + const delta = note.type === 'CREDIT' ? -note.totalAmount : note.totalAmount; + this.updatePartyBalance(note.partyName, delta); + + return record; + } + + getNotes(type?: 'CREDIT' | 'DEBIT'): NoteRecord[] { + if (!type) return this.notes; + return this.notes.filter(n => n.type === type); + } + + // --- BANKING METHODS --- + addBankAccount(acc: Omit): BankAccountRecord { + const id = `BNK-${String(this.bankAccounts.length + 1).padStart(3, '0')}`; + const record: BankAccountRecord = { id, ...acc }; + this.bankAccounts.push(record); + return record; + } + + getBankAccounts(): BankAccountRecord[] { + return this.bankAccounts; + } + + recordCheque(chq: Omit): ChequeRecord { + const id = `CHQ-${String(this.cheques.length + 1).padStart(3, '0')}`; + const record: ChequeRecord = { id, ...chq }; + this.cheques.push(record); + return record; + } + + getCheques(): ChequeRecord[] { + return this.cheques; + } +} + +export const erpStore = new ERPStore(); diff --git a/sample-apps/alphatex-mcp/src/lib/invoice-html-template.ts b/sample-apps/alphatex-mcp/src/lib/invoice-html-template.ts new file mode 100644 index 000000000..6d9621cd6 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/lib/invoice-html-template.ts @@ -0,0 +1,301 @@ +/** + * Generates self-contained, pixel-perfect Tally GST Tax Invoice HTML for print and viewing + */ + +export function generateTallyInvoiceHTML(invoice: any): string { + const fmt = (val: number = 0) => + val.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + + const itemsRows = (invoice.items || []) + .map( + (item: any, idx: number) => ` + + ${idx + 1} + ${item.description} + ${item.hsn} + ${item.qty} + ₹ ${fmt(item.rate)} + ${item.unit || 'Kgs'} + ₹ ${fmt(item.lineTotal)} + + ` + ) + .join(''); + + const hsnRows = (invoice.hsnSummary || []) + .map( + (hsn: any) => ` + + ${hsn.hsn} + ₹ ${fmt(hsn.taxableValue)} + ${ + !invoice.isInterstate + ? ` + ${hsn.taxRatePct / 2}% + ₹ ${fmt(hsn.cgstAmount)} + ${hsn.taxRatePct / 2}% + ₹ ${fmt(hsn.sgstAmount)} + ` + : ` + ${hsn.taxRatePct}% + ₹ ${fmt(hsn.igstAmount)} + ` + } + ₹ ${fmt(hsn.totalTax)} + + ` + ) + .join(''); + + const totalQty = (invoice.items || []).reduce((acc: number, i: any) => acc + (i.qty || 0), 0); + + return ` + + + + Tax Invoice - ${invoice.invoiceNo} + + + + +
+ +
+ +
+ +
+ TAX INVOICE + (ORIGINAL FOR RECIPIENT) +
+ + + + + + + + + + + + + + + + + + + + + +
+
Seller
+
${invoice.seller.name}
+
${invoice.seller.address}
+
Phone: ${invoice.seller.phone}
+
Email: ${invoice.seller.email}
+
GSTIN/UIN: ${invoice.seller.gstin}
+
PAN: ${invoice.seller.pan || 'AAAAA0000A'}
+
+
Invoice No.
+
${invoice.invoiceNo}
+
+
Dated
+
${invoice.invoiceDate}
+
+
Delivery Note
+
Standard Dispatch
+
+
Mode/Terms of Payment
+
${invoice.paymentTerms || 'Credit - 15 Days'}
+
+
Other Reference(s)
+
GST Supply Type: ${invoice.isInterstate ? 'Interstate (IGST)' : 'Intrastate (CGST + SGST)'}
+
+
Buyer (Billed To)
+
${invoice.party.name}
+
${invoice.party.address}
+
Phone: ${invoice.party.phone || 'N/A'}
+
GSTIN/UIN: ${invoice.party.gstin}
+
State: ${invoice.party.state} (${invoice.party.stateCode})
+
+
Terms of Delivery
+
${invoice.notes || '1. Goods once sold will not be taken back. 2. Tirupur jurisdiction.'}
+
+ + + + + + + + + + + + + + + + ${itemsRows} + + ${ + invoice.freightCharges > 0 + ? ` + + + + + + + + + + ` + : '' + } + + ${ + invoice.roundOff !== 0 + ? ` + + + + + + + + + + ` + : '' + } + + + + + + + + + +
Sl No.Description of GoodsHSN/SACQuantityRateUnitAmount (₹)
-Freight & Handling Charges9965---₹ ${fmt(invoice.freightCharges)}
-Round Off----${invoice.roundOff > 0 ? `+${invoice.roundOff}` : invoice.roundOff}
Total${totalQty}₹ ${fmt(invoice.grandTotal)}
+ + +
+
+ Amount Chargeable (in words):
+ ${invoice.amountInWords} +
+
E. & O.E.
+
+
+ + + + + + + + ${ + !invoice.isInterstate + ? ` + + + ` + : ` + + ` + } + + + + ${ + !invoice.isInterstate + ? ` + + + + + ` + : ` + + + ` + } + + + + ${hsnRows} + +
HSN/SACTaxable Value (₹)Central Tax (CGST)State Tax (SGST)Integrated Tax (IGST)Total Tax Amount (₹)
RateAmountRateAmountRateAmount
+ + + + + + + + + + + + +
+
Company PAN: ${invoice.seller.pan || 'AAAAA0000A'}
+
+ Company's Bank Details:
+ Bank Name: ${invoice.bankDetails?.bankName || 'State Bank of India'}
+ A/c No.: ${invoice.bankDetails?.accountNo || '39847192834'}
+ Branch & IFS Code: ${invoice.bankDetails?.ifsc || 'SBIN0001234'} (${invoice.bankDetails?.branch || 'Tirupur Main'}) +
+
+
for ${invoice.seller.name}
+
Authorised Signatory
+
+ Declaration:
+ 1. We declare that this invoice shows the actual price of the goods described and that all particulars are true and correct.
+ 2. All disputes are subject to Tirupur jurisdiction. +
+
+ + +`; +} diff --git a/sample-apps/alphatex-mcp/src/modules/invoice/invoice.module.ts b/sample-apps/alphatex-mcp/src/modules/invoice/invoice.module.ts new file mode 100644 index 000000000..3e9b97cd6 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/modules/invoice/invoice.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nitrostack/core'; +import { InvoiceTools } from '../../tools/invoice.tools.js'; +import { ItemTools } from '../../tools/item.tools.js'; +import { PartyTools } from '../../tools/party.tools.js'; +import { NoteTools } from '../../tools/note.tools.js'; +import { PaymentTools } from '../../tools/payment.tools.js'; +import { BankingTools } from '../../tools/banking.tools.js'; + +@Module({ + name: 'alphatex_platform', + description: 'AlphaTex ERP Complete Platform Modules (Invoices, Inventory, Parties, Credit/Debit Notes, Payments, Ledgers, Banking)', + controllers: [ + InvoiceTools, + ItemTools, + PartyTools, + NoteTools, + PaymentTools, + BankingTools, + ], +}) +export class InvoiceModule {} diff --git a/sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.data.ts b/sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.data.ts new file mode 100644 index 000000000..56e90a9d5 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/modules/pizzaz/pizzaz.module.ts b/sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.module.ts new file mode 100644 index 000000000..0af8e1840 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/modules/pizzaz/pizzaz.service.ts b/sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.service.ts new file mode 100644 index 000000000..0db741371 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/modules/pizzaz/pizzaz.tasks.ts b/sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.tasks.ts new file mode 100644 index 000000000..d0147ed21 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/modules/pizzaz/pizzaz.tools.ts b/sample-apps/alphatex-mcp/src/modules/pizzaz/pizzaz.tools.ts new file mode 100644 index 000000000..4997951d7 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/tools/banking.tools.ts b/sample-apps/alphatex-mcp/src/tools/banking.tools.ts new file mode 100644 index 000000000..4a7d72a96 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/tools/banking.tools.ts @@ -0,0 +1,79 @@ +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { erpStore } from '../lib/erp-store.js'; + +export const AddBankSchema = z.object({ + bankName: z.string().describe('Bank Name (e.g. State Bank of India, HDFC Bank)'), + accountNo: z.string().describe('Account Number'), + ifsc: z.string().describe('IFSC Code (e.g. SBIN0001234)'), + branch: z.string().optional().default('Main Branch').describe('Branch Name'), + accountType: z.string().optional().default('Current Account').describe('Account Type'), +}); + +export const RecordChequeSchema = z.object({ + chequeNo: z.string().describe('6-digit Cheque Number (e.g. 004921)'), + date: z.string().optional().describe('Cheque Date (YYYY-MM-DD)'), + partyName: z.string().describe('Party / Payee Name'), + amount: z.number().positive().describe('Cheque Amount in INR'), + bankName: z.string().optional().default('State Bank of India').describe('Bank Name'), + status: z.enum(['PENDING', 'CLEARED', 'BOUNCED']).optional().default('PENDING').describe('Clearing Status'), + type: z.enum(['ISSUED', 'RECEIVED']).optional().default('ISSUED').describe('ISSUED to supplier or RECEIVED from customer'), +}); + +@Injectable() +export class BankingTools { + @Tool({ + name: 'manage_bank_account', + description: 'Add or list company bank accounts for cheque printing and NEFT transfers.', + inputSchema: AddBankSchema, + }) + async manageBank(args: z.infer, ctx: ExecutionContext) { + const bank = erpStore.addBankAccount({ + bankName: args.bankName, + accountNo: args.accountNo, + ifsc: args.ifsc, + branch: args.branch || 'Main Branch', + accountType: args.accountType || 'Current Account', + }); + + return { + message: `Bank account '${bank.bankName} (${bank.accountNo})' added!`, + bank, + allAccounts: erpStore.getBankAccounts(), + }; + } + + @Tool({ + name: 'record_cheque', + description: 'Record an issued or received cheque for printing or tracking clearing status.', + inputSchema: RecordChequeSchema, + }) + async recordCheque(args: z.infer, ctx: ExecutionContext) { + const cheque = erpStore.recordCheque({ + chequeNo: args.chequeNo, + date: args.date || new Date().toISOString().split('T')[0], + partyName: args.partyName, + amount: args.amount, + bankName: args.bankName || 'State Bank of India', + status: args.status || 'PENDING', + type: args.type || 'ISSUED', + }); + + return { + message: `Cheque #${cheque.chequeNo} recorded for ${cheque.partyName}!`, + cheque, + }; + } + + @Tool({ + name: 'get_cheque_register', + description: 'View cheque register tracking all issued and received cheques and their clearing status.', + inputSchema: z.object({}), + }) + async getChequeRegister(args: {}, ctx: ExecutionContext) { + const cheques = erpStore.getCheques(); + return { + totalCheques: cheques.length, + cheques, + }; + } +} diff --git a/sample-apps/alphatex-mcp/src/tools/invoice.tools.ts b/sample-apps/alphatex-mcp/src/tools/invoice.tools.ts new file mode 100644 index 000000000..13ab8f221 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/tools/invoice.tools.ts @@ -0,0 +1,203 @@ +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { calculateInvoiceTotals } from '../lib/erp-engine.js'; +import { erpStore } from '../lib/erp-store.js'; +import { generateTallyInvoiceHTML } from '../lib/invoice-html-template.js'; +import fs from 'fs'; +import path from 'path'; +import { exec } from 'child_process'; + +export const SellerSchema = z.object({ + name: z.string().optional().describe('Seller / Your Business Name (e.g. ALPHATEX ENTERPRISES)'), + gstin: z.string().optional().describe('Seller GSTIN Number'), + pan: z.string().optional().describe('Seller PAN Number'), + address: z.string().optional().describe('Seller Address'), + phone: z.string().optional().describe('Seller Phone Number'), + email: z.string().optional().describe('Seller Email Address'), +}); + +export const PartySchema = z.object({ + name: z.string().optional().describe('Party/Customer Business Name'), + gstin: z.string().optional().describe('Party GSTIN Number'), + address: z.string().optional().describe('Party Address'), + state: z.string().optional().describe('State Name'), + stateCode: z.string().optional().describe('State Code (e.g. 33 for Tamil Nadu)'), + phone: z.string().optional().describe('Phone Number'), +}); + +export const InvoiceItemSchema = z.object({ + description: z.string().optional().describe('Item Description or Product Name'), + hsn: z.string().optional().describe('HSN or SAC code'), + qty: z.number().optional().describe('Quantity'), + rate: z.number().optional().describe('Unit Rate / Price per unit'), + unit: z.string().optional().default('Kgs').describe('Unit of measurement (Kgs, Meters, Pcs, Bales)'), + discountPct: z.number().min(0).max(100).optional().default(0).describe('Discount percentage for this line item'), + taxRatePct: z.number().min(0).max(100).optional().default(5).describe('GST Tax Rate percentage (5, 12, 18, 28)'), +}); + +export const GenerateInvoiceSchema = z.object({ + invoiceNo: z.string().optional().describe('Invoice Number (e.g. AT/2026/001)'), + invoiceDate: z.string().optional().describe('Invoice Date (YYYY-MM-DD)'), + dueDate: z.string().optional().describe('Payment Due Date (YYYY-MM-DD)'), + seller: SellerSchema.optional().describe('Your Company / Seller Information'), + partyName: z.string().optional().describe('Customer Name'), + party: PartySchema.optional().describe('Customer / Buyer Billing Information'), + items: z.array(InvoiceItemSchema).optional().describe('List of Invoice Line Items'), + isInterstate: z.boolean().optional().default(false).describe('True if Interstate (IGST), False for Intrastate (CGST + SGST)'), + invoiceDiscountPct: z.number().min(0).max(100).optional().default(0).describe('Overall Invoice Discount %'), + freightCharges: z.number().nonnegative().optional().default(0).describe('Freight & Transport Charges'), + packingCharges: z.number().nonnegative().optional().default(0).describe('Packing Charges'), + paymentTerms: z.string().optional().default('Credit - 15 Days').describe('Payment Mode / Terms'), + notes: z.string().optional().describe('Invoice Terms & Notes'), + autoOpen: z.boolean().optional().default(true).describe('Automatically open the generated HTML bill in browser'), +}); + +@Injectable() +export class InvoiceTools { + @Tool({ + name: 'generate_alphatex_invoice', + description: 'Generates a GST compliant Tax Invoice, formats text summary, and automatically creates & opens the printable Tally GST bill in browser.', + inputSchema: GenerateInvoiceSchema, + }) + async generateInvoice(args: z.infer, ctx: ExecutionContext) { + // 1. Resolve Seller + const sellerInfo = { + name: args.seller?.name || 'ALPHATEX ENTERPRISES', + gstin: args.seller?.gstin || '33AAAAA0000A1Z5', + pan: args.seller?.pan || 'AAAAA0000A', + address: args.seller?.address || '123 Textile Park, Tirupur, Tamil Nadu - 641601', + phone: args.seller?.phone || '+91 98765 43210', + email: args.seller?.email || 'billing@alphatex.com', + }; + + // 2. Resolve Party + let partyObj = args.party; + if (!partyObj && args.partyName) { + const found = erpStore.getPartyByName(args.partyName); + if (found) { + partyObj = { + name: found.name, + gstin: found.gstin, + address: found.address, + state: found.state, + stateCode: found.stateCode, + phone: found.phone, + }; + } + } + + const partyInfo = { + name: partyObj?.name || args.partyName || 'Vardhaman Spinning Mills', + gstin: partyObj?.gstin || '33BBBBB1111B2Z2', + address: partyObj?.address || '45 Fashion Highway, Erode, Tamil Nadu - 638001', + state: partyObj?.state || 'Tamil Nadu', + stateCode: partyObj?.stateCode || '33', + phone: partyObj?.phone || '+91 91234 56789', + }; + + // 3. Resolve Items + const rawItems = (args.items && args.items.length > 0) + ? args.items.map(i => ({ + description: i.description || 'Bio-Wash Hosiery Fabric', + hsn: i.hsn || '6006', + qty: i.qty || 300, + rate: i.rate || 450, + unit: i.unit || 'Kgs', + discountPct: i.discountPct || 0, + taxRatePct: i.taxRatePct ?? 12, + })) + : [ + { + description: 'Bio-Wash Hosiery Fabric', + hsn: '6006', + qty: 300, + rate: 450, + unit: 'Kgs', + discountPct: 0, + taxRatePct: 12, + }, + ]; + + ctx.logger.info('Generating AlphaTex Invoice', { seller: sellerInfo.name, party: partyInfo.name, itemCount: rawItems.length }); + + const calculatedTotals = calculateInvoiceTotals(rawItems, { + isInterstate: args.isInterstate, + invoiceDiscountPct: args.invoiceDiscountPct, + freightCharges: args.freightCharges, + packingCharges: args.packingCharges, + autoRound: true, + }); + + const sanitizedInvoiceNo = (args.invoiceNo || `AT-${Date.now().toString().slice(-6)}`).replace(/[^a-zA-Z0-9_-]/g, '_'); + const invoiceNo = args.invoiceNo || `AT-${Date.now().toString().slice(-6)}`; + const invoiceDate = args.invoiceDate || new Date().toISOString().split('T')[0]; + const bankAccount = erpStore.getBankAccounts()[0] || { + bankName: 'State Bank of India', + accountNo: '39847192834', + ifsc: 'SBIN0001234', + branch: 'Main Branch, Tirupur', + }; + + const invoiceData = { + invoiceNo, + invoiceDate, + dueDate: args.dueDate || invoiceDate, + paymentTerms: args.paymentTerms || 'Credit - 15 Days', + party: partyInfo, + seller: sellerInfo, + bankDetails: bankAccount, + isInterstate: Boolean(args.isInterstate), + notes: args.notes || 'Thank you for your business! Payment due within 15 days.', + ...calculatedTotals, + }; + + // 4. Generate & Save Standalone Tally GST Invoice HTML file to Desktop + const htmlContent = generateTallyInvoiceHTML(invoiceData); + const desktopDir = 'C:\\Users\\raiyan\\OneDrive\\Desktop'; + const desktopFilePath = path.join(desktopDir, `AlphaTex_Invoice_${sanitizedInvoiceNo}.html`); + + try { + fs.writeFileSync(desktopFilePath, htmlContent, 'utf-8'); + if (args.autoOpen !== false) { + exec(`start "" "${desktopFilePath}"`); + } + } catch (err) { + ctx.logger.warn('Error saving/opening Desktop invoice', { error: String(err) }); + } + + const fmt = (val: number) => val.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + + // Format rich text markdown summary + const markdownSummary = ` +===================================================== + TAX INVOICE - ${invoiceNo} +===================================================== +Seller: ${sellerInfo.name} | GSTIN: ${sellerInfo.gstin} +Buyer: ${partyInfo.name} | GSTIN: ${partyInfo.gstin} +Date: ${invoiceDate} | Mode: ${invoiceData.paymentTerms} +----------------------------------------------------- +ITEMS: +${rawItems.map((item, idx) => `${idx + 1}. ${item.description} (HSN: ${item.hsn}) + Qty: ${item.qty} ${item.unit} @ ₹${item.rate} = Taxable: ₹${fmt(calculatedTotals.items[idx].taxableValue)} (GST ${item.taxRatePct}%)`).join('\n')} +----------------------------------------------------- +Subtotal Taxable: ₹${fmt(calculatedTotals.totalTaxableValue)} +${!args.isInterstate ? `CGST (6%): ₹${fmt(calculatedTotals.totalCGST)}\nSGST (6%): ₹${fmt(calculatedTotals.totalSGST)}` : `IGST (12%): ₹${fmt(calculatedTotals.totalIGST)}`} +----------------------------------------------------- +GRAND TOTAL: ₹${fmt(calculatedTotals.grandTotal)} +AMOUNT IN WORDS: ${calculatedTotals.amountInWords} +===================================================== +📄 Printable GST Bill Saved & Opened: +file:///${desktopFilePath.replace(/\\/g, '/')} +===================================================== +`; + + return { + status: 'SUCCESS', + invoiceNo, + grandTotal: calculatedTotals.grandTotal, + amountInWords: calculatedTotals.amountInWords, + desktopBillFile: desktopFilePath, + summary: markdownSummary, + data: invoiceData, + }; + } +} diff --git a/sample-apps/alphatex-mcp/src/tools/item.tools.ts b/sample-apps/alphatex-mcp/src/tools/item.tools.ts new file mode 100644 index 000000000..ccff89268 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/tools/item.tools.ts @@ -0,0 +1,104 @@ +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { erpStore } from '../lib/erp-store.js'; + +export const CreateItemSchema = z.object({ + name: z.string().describe('Item Name or Product Description (e.g. 100% Combed Cotton Yarn 30s)'), + code: z.string().optional().describe('SKU / Item Code (e.g. YRN-30S)'), + hsn: z.string().optional().default('5205').describe('HSN or SAC Code'), + unit: z.string().optional().default('Kgs').describe('Unit of measurement (Kgs, Meters, Pcs, Bales)'), + rate: z.number().positive().describe('Default Unit Price Rate in INR'), + taxRatePct: z.number().min(0).max(100).optional().default(5).describe('GST Tax Rate Percentage (5, 12, 18, 28)'), + initialStock: z.number().min(0).optional().default(0).describe('Initial opening stock quantity'), + minStockWarning: z.number().min(0).optional().default(50).describe('Minimum stock warning threshold'), +}); + +export const ListItemsSchema = z.object({ + searchQuery: z.string().optional().describe('Search item by name, code, or HSN'), + lowStockOnly: z.boolean().optional().default(false).describe('If true, filter to items below min stock threshold'), +}); + +export const UpdateStockSchema = z.object({ + itemId: z.string().describe('Item ID (e.g. ITM-001) or Item Name'), + quantityChange: z.number().describe('Quantity to add (positive) or deduct (negative)'), + reason: z.string().optional().describe('Reason for stock adjustment'), +}); + +@Injectable() +export class ItemTools { + @Tool({ + name: 'create_item', + description: 'Add a new product or raw material item to AlphaTex inventory with default price, HSN, and GST rate.', + inputSchema: CreateItemSchema, + }) + async createItem(args: z.infer, ctx: ExecutionContext) { + ctx.logger.info('Creating new item', { name: args.name, rate: args.rate }); + + const newItem = erpStore.addItem({ + name: args.name, + code: args.code || `SKU-${Date.now().toString().slice(-4)}`, + hsn: args.hsn || '5205', + unit: args.unit || 'Kgs', + rate: args.rate, + taxRatePct: args.taxRatePct ?? 5, + currentStock: args.initialStock ?? 0, + minStockWarning: args.minStockWarning ?? 50, + }); + + return { + message: `Product '${newItem.name}' added successfully to stock inventory!`, + item: newItem, + }; + } + + @Tool({ + name: 'list_items', + description: 'List products and check current stock levels, rates, HSN codes, and low stock warnings.', + inputSchema: ListItemsSchema, + }) + async listItems(args: z.infer, ctx: ExecutionContext) { + ctx.logger.info('Listing items', { query: args.searchQuery }); + + let items = erpStore.getItems(); + + if (args.searchQuery) { + const q = args.searchQuery.toLowerCase(); + items = items.filter( + i => i.name.toLowerCase().includes(q) || i.code.toLowerCase().includes(q) || i.hsn.includes(q) + ); + } + + if (args.lowStockOnly) { + items = items.filter(i => i.currentStock <= i.minStockWarning); + } + + return { + totalItems: items.length, + items: items.map(i => ({ + ...i, + stockStatus: i.currentStock <= i.minStockWarning ? 'LOW STOCK WARNING' : 'OK', + })), + }; + } + + @Tool({ + name: 'update_item_stock', + description: 'Adjust stock quantity for an item (add received goods or deduct samples).', + inputSchema: UpdateStockSchema, + }) + async updateStock(args: z.infer, ctx: ExecutionContext) { + let item = erpStore.getItemByQuery(args.itemId); + + if (!item) { + return { error: `Item matching '${args.itemId}' was not found in inventory.` }; + } + + const updated = erpStore.updateStock(item.id, args.quantityChange); + + return { + message: `Stock updated for '${item.name}'!`, + previousStock: item.currentStock - args.quantityChange, + newStock: updated?.currentStock, + reason: args.reason || 'Manual Adjustment', + }; + } +} diff --git a/sample-apps/alphatex-mcp/src/tools/note.tools.ts b/sample-apps/alphatex-mcp/src/tools/note.tools.ts new file mode 100644 index 000000000..9ddcf25eb --- /dev/null +++ b/sample-apps/alphatex-mcp/src/tools/note.tools.ts @@ -0,0 +1,64 @@ +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { erpStore } from '../lib/erp-store.js'; +import { round2, numberToWordsINR } from '../lib/erp-engine.js'; + +export const IssueNoteSchema = z.object({ + type: z.enum(['CREDIT', 'DEBIT']).describe('CREDIT for Sales Return / Rate Diff discount, DEBIT for Purchase Return / Extra billing'), + partyName: z.string().describe('Customer or Supplier Name'), + date: z.string().optional().describe('Date of Credit/Debit Note (YYYY-MM-DD)'), + reason: z.string().describe('Reason for issuing note (e.g. Sales Return of Damaged Yarn, Rate Difference adjustment)'), + amount: z.number().positive().describe('Taxable Amount in INR'), + taxRatePct: z.number().min(0).max(100).optional().default(5).describe('GST Tax Rate percentage (5, 12, 18)'), +}); + +export const ListNotesSchema = z.object({ + typeFilter: z.enum(['CREDIT', 'DEBIT']).optional().describe('Filter by note type'), +}); + +@Injectable() +export class NoteTools { + @Tool({ + name: 'issue_credit_or_debit_note', + description: 'Issue a GST Credit Note (sales return / discount) or Debit Note (purchase return / debit charge) to a party.', + inputSchema: IssueNoteSchema, + }) + async issueNote(args: z.infer, ctx: ExecutionContext) { + ctx.logger.info('Issuing note', { type: args.type, party: args.partyName }); + + const taxAmount = round2((args.amount * args.taxRatePct) / 100); + const totalAmount = round2(args.amount + taxAmount); + const noteNo = `${args.type === 'CREDIT' ? 'CN' : 'DN'}-${Date.now().toString().slice(-6)}`; + + const newNote = erpStore.addNote({ + noteNo, + type: args.type, + partyName: args.partyName, + date: args.date || new Date().toISOString().split('T')[0], + reason: args.reason, + amount: args.amount, + taxRatePct: args.taxRatePct, + totalAmount, + }); + + return { + message: `${args.type === 'CREDIT' ? 'Credit Note' : 'Debit Note'} ${newNote.noteNo} issued successfully!`, + note: newNote, + taxAmount, + totalAmount, + amountInWords: numberToWordsINR(totalAmount), + }; + } + + @Tool({ + name: 'list_notes', + description: 'List issued Credit Notes and Debit Notes with reason, party name, tax amount, and total.', + inputSchema: ListNotesSchema, + }) + async listNotes(args: z.infer, ctx: ExecutionContext) { + const notes = erpStore.getNotes(args.typeFilter); + return { + totalNotes: notes.length, + notes, + }; + } +} diff --git a/sample-apps/alphatex-mcp/src/tools/party.tools.ts b/sample-apps/alphatex-mcp/src/tools/party.tools.ts new file mode 100644 index 000000000..dd19c6a60 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/tools/party.tools.ts @@ -0,0 +1,87 @@ +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { erpStore } from '../lib/erp-store.js'; + +export const CreatePartySchema = z.object({ + name: z.string().describe('Customer or Supplier Business Name (e.g. Apex Fabrics Ltd.)'), + gstin: z.string().optional().describe('15-digit GSTIN (e.g. 33BBBBB1111B2Z2)'), + phone: z.string().optional().describe('Contact Phone Number'), + email: z.string().optional().describe('Contact Email Address'), + address: z.string().optional().describe('Billing / Business Address'), + state: z.string().optional().default('Tamil Nadu').describe('State Name'), + stateCode: z.string().optional().default('33').describe('State Code (33 for TN, 03 for Punjab, etc.)'), + openingBalance: z.number().optional().default(0).describe('Opening balance amount in INR'), + balanceType: z.enum(['Receivable', 'Payable']).optional().default('Receivable').describe('Receivable if customer owes you, Payable if you owe supplier'), +}); + +export const ListPartiesSchema = z.object({ + searchQuery: z.string().optional().describe('Filter party by name, phone, or GSTIN'), +}); + +export const GetPartySchema = z.object({ + partyName: z.string().describe('Party Name or GSTIN to look up'), +}); + +@Injectable() +export class PartyTools { + @Tool({ + name: 'create_party', + description: 'Add a new customer or supplier business record with GSTIN, contact details, and opening balance.', + inputSchema: CreatePartySchema, + }) + async createParty(args: z.infer, ctx: ExecutionContext) { + ctx.logger.info('Creating party record', { name: args.name }); + + const newParty = erpStore.addParty({ + name: args.name, + gstin: args.gstin || 'UNREGISTERED', + phone: args.phone || '', + email: args.email || '', + address: args.address || 'N/A', + state: args.state || 'Tamil Nadu', + stateCode: args.stateCode || '33', + openingBalance: args.openingBalance ?? 0, + balanceType: args.balanceType || 'Receivable', + }); + + return { + message: `Party '${newParty.name}' added successfully!`, + party: newParty, + }; + } + + @Tool({ + name: 'list_parties', + description: 'List registered customers and suppliers with current balance, GSTIN, and contact details.', + inputSchema: ListPartiesSchema, + }) + async listParties(args: z.infer, ctx: ExecutionContext) { + let parties = erpStore.getParties(); + + if (args.searchQuery) { + const q = args.searchQuery.toLowerCase(); + parties = parties.filter( + p => p.name.toLowerCase().includes(q) || p.gstin.toLowerCase().includes(q) || p.phone.includes(q) + ); + } + + return { + totalParties: parties.length, + parties, + }; + } + + @Tool({ + name: 'get_party_details', + description: 'Get complete profile, GSTIN, address, and current account balance for a specific customer/supplier.', + inputSchema: GetPartySchema, + }) + async getPartyDetails(args: z.infer, ctx: ExecutionContext) { + const party = erpStore.getPartyByName(args.partyName); + + if (!party) { + return { error: `No customer or supplier matching '${args.partyName}' was found.` }; + } + + return { party }; + } +} diff --git a/sample-apps/alphatex-mcp/src/tools/payment.tools.ts b/sample-apps/alphatex-mcp/src/tools/payment.tools.ts new file mode 100644 index 000000000..aafc0cf7c --- /dev/null +++ b/sample-apps/alphatex-mcp/src/tools/payment.tools.ts @@ -0,0 +1,74 @@ +import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core'; +import { erpStore } from '../lib/erp-store.js'; + +export const RecordPaymentSchema = z.object({ + partyName: z.string().describe('Customer or Supplier Business Name'), + amount: z.number().positive().describe('Payment Amount in INR'), + date: z.string().optional().describe('Payment Date (YYYY-MM-DD)'), + type: z.enum(['RECEIVED', 'PAID']).optional().default('RECEIVED').describe('RECEIVED from customer, PAID to supplier'), + paymentMode: z.enum(['CASH', 'CHEQUE', 'UPI', 'NEFT']).optional().default('NEFT').describe('Payment Mode'), + referenceNo: z.string().optional().describe('UTR / Transaction Reference No / Cheque No'), + notes: z.string().optional().describe('Additional notes or bank remark'), +}); + +export const GetLedgerSchema = z.object({ + partyName: z.string().describe('Customer or Supplier Business Name'), +}); + +@Injectable() +export class PaymentTools { + @Tool({ + name: 'record_payment', + description: 'Record a payment received from customer or paid to supplier via Cash, UPI, Cheque, or NEFT.', + inputSchema: RecordPaymentSchema, + }) + async recordPayment(args: z.infer, ctx: ExecutionContext) { + ctx.logger.info('Recording payment', { party: args.partyName, amount: args.amount }); + + const payment = erpStore.recordPayment({ + partyName: args.partyName, + amount: args.amount, + date: args.date || new Date().toISOString().split('T')[0], + type: args.type || 'RECEIVED', + paymentMode: args.paymentMode || 'NEFT', + referenceNo: args.referenceNo || `REF-${Date.now().toString().slice(-6)}`, + notes: args.notes || '', + }); + + const party = erpStore.getPartyByName(args.partyName); + + return { + message: `Payment of ₹ ${args.amount.toLocaleString('en-IN')} ${args.type === 'RECEIVED' ? 'received from' : 'paid to'} ${args.partyName} recorded!`, + payment, + updatedCustomerBalance: party ? party.currentBalance : undefined, + }; + } + + @Tool({ + name: 'get_customer_ledger', + description: 'Get customer/supplier statement ledger including opening balance, payments, invoices, notes, and current balance.', + inputSchema: GetLedgerSchema, + }) + async getLedger(args: z.infer, ctx: ExecutionContext) { + const party = erpStore.getPartyByName(args.partyName); + + if (!party) { + return { error: `Party '${args.partyName}' was not found.` }; + } + + const partyPayments = erpStore.getPayments(args.partyName); + + return { + party: { + name: party.name, + gstin: party.gstin, + phone: party.phone, + openingBalance: party.openingBalance, + balanceType: party.balanceType, + currentBalance: party.currentBalance, + }, + paymentTransactions: partyPayments, + summary: `Current outstanding balance for ${party.name} is ₹ ${party.currentBalance.toLocaleString('en-IN')}.`, + }; + } +} diff --git a/sample-apps/alphatex-mcp/src/widgets/app/globals.css b/sample-apps/alphatex-mcp/src/widgets/app/globals.css new file mode 100644 index 000000000..6a2704868 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/widgets/app/globals.css @@ -0,0 +1,10 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; +} diff --git a/sample-apps/alphatex-mcp/src/widgets/app/invoice-viewer/page.tsx b/sample-apps/alphatex-mcp/src/widgets/app/invoice-viewer/page.tsx new file mode 100644 index 000000000..d90310ace --- /dev/null +++ b/sample-apps/alphatex-mcp/src/widgets/app/invoice-viewer/page.tsx @@ -0,0 +1,9 @@ +'use client'; + +import InvoiceViewerWidget from '../../invoice-viewer'; + +export const dynamic = 'force-dynamic'; + +export default function InvoiceViewerPage() { + return ; +} diff --git a/sample-apps/alphatex-mcp/src/widgets/app/layout.tsx b/sample-apps/alphatex-mcp/src/widgets/app/layout.tsx new file mode 100644 index 000000000..9c8290831 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/widgets/app/layout.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { WidgetLayout } from '@nitrostack/widgets'; +import './globals.css'; +import 'mapbox-gl/dist/mapbox-gl.css'; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/sample-apps/alphatex-mcp/src/widgets/app/pizza-list/page.tsx b/sample-apps/alphatex-mcp/src/widgets/app/pizza-list/page.tsx new file mode 100644 index 000000000..fbabe8606 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/widgets/app/pizza-map/page.tsx b/sample-apps/alphatex-mcp/src/widgets/app/pizza-map/page.tsx new file mode 100644 index 000000000..78afa6ca0 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/widgets/app/pizza-shop/page.tsx b/sample-apps/alphatex-mcp/src/widgets/app/pizza-shop/page.tsx new file mode 100644 index 000000000..4f2a731eb --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/widgets/components/CompactShopCard.tsx b/sample-apps/alphatex-mcp/src/widgets/components/CompactShopCard.tsx new file mode 100644 index 000000000..7799ba9e7 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/widgets/components/PizzaCard.tsx b/sample-apps/alphatex-mcp/src/widgets/components/PizzaCard.tsx new file mode 100644 index 000000000..7821977fc --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/widgets/invoice-viewer.tsx b/sample-apps/alphatex-mcp/src/widgets/invoice-viewer.tsx new file mode 100644 index 000000000..1582a8cb4 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/widgets/invoice-viewer.tsx @@ -0,0 +1,604 @@ +'use client'; + +import React from 'react'; +import { useTheme, useWidgetSDK } from '@nitrostack/widgets'; +import { Printer, Sparkles, Building2, UserCheck, FileText, CheckCircle2, DollarSign } from 'lucide-react'; + +export interface CalculatedLineItem { + description: string; + hsn: string; + qty: number; + rate: number; + unit?: string; + grossAmount: number; + discountPct: number; + discountAmount: number; + taxableValue: number; + taxRatePct: number; + cgstRate: number; + cgstAmount: number; + sgstRate: number; + sgstAmount: number; + igstRate: number; + igstAmount: number; + totalTax: number; + lineTotal: number; +} + +export interface HsnSummaryItem { + hsn: string; + taxableValue: number; + taxRatePct: number; + cgstAmount: number; + sgstAmount: number; + igstAmount: number; + totalTax: number; +} + +export interface InvoiceData { + invoiceNo: string; + invoiceDate: string; + dueDate?: string; + paymentTerms?: string; + party: { + name: string; + gstin: string; + address: string; + state: string; + stateCode: string; + phone?: string; + }; + seller: { + name: string; + gstin: string; + pan?: string; + address: string; + phone: string; + email: string; + }; + bankDetails?: { + bankName: string; + accountNo: string; + ifsc: string; + branch?: string; + }; + isInterstate: boolean; + items: CalculatedLineItem[]; + hsnSummary: HsnSummaryItem[]; + subtotalGross: number; + totalItemDiscount: number; + totalTaxableValue: number; + invoiceDiscountPct: number; + invoiceDiscountAmount: number; + freightCharges: number; + packingCharges: number; + netTaxableValue: number; + totalCGST: number; + totalSGST: number; + totalIGST: number; + totalTaxAmount: number; + unroundedGrandTotal: number; + roundOff: number; + grandTotal: number; + amountInWords: string; + notes?: string; +} + +const DEFAULT_PREVIEW_INVOICE: InvoiceData = { + invoiceNo: 'AT-2026-001', + invoiceDate: '2026-08-01', + dueDate: '2026-08-16', + paymentTerms: 'Credit - 15 Days', + seller: { + name: 'ALPHATEX ENTERPRISES', + gstin: '33AAAAA0000A1Z5', + pan: 'AAAAA0000A', + address: '123 Textile Park, Tirupur, Tamil Nadu - 641601', + phone: '+91 98765 43210', + email: 'billing@alphatex.com', + }, + party: { + name: 'Vardhaman Spinning Mills', + gstin: '33BBBBB1111B2Z2', + address: '45 Fashion Highway, Erode, Tamil Nadu - 638001', + state: 'Tamil Nadu', + stateCode: '33', + phone: '+91 91234 56789', + }, + bankDetails: { + bankName: 'State Bank of India', + accountNo: '39847192834', + ifsc: 'SBIN0001234', + branch: 'Main Branch, Tirupur', + }, + isInterstate: false, + items: [ + { + description: 'Bio-Wash Hosiery Fabric', + hsn: '6006', + qty: 300, + rate: 450, + unit: 'Kgs', + grossAmount: 135000, + discountPct: 0, + discountAmount: 0, + taxableValue: 135000, + taxRatePct: 12, + cgstRate: 6, + cgstAmount: 8100, + sgstRate: 6, + sgstAmount: 8100, + igstRate: 0, + igstAmount: 0, + totalTax: 16200, + lineTotal: 151200, + }, + ], + hsnSummary: [ + { + hsn: '6006', + taxableValue: 135000, + taxRatePct: 12, + cgstAmount: 8100, + sgstAmount: 8100, + igstAmount: 0, + totalTax: 16200, + }, + ], + subtotalGross: 135000, + totalItemDiscount: 0, + totalTaxableValue: 135000, + invoiceDiscountPct: 0, + invoiceDiscountAmount: 0, + freightCharges: 0, + packingCharges: 0, + netTaxableValue: 135000, + totalCGST: 8100, + totalSGST: 8100, + totalIGST: 0, + totalTaxAmount: 16200, + unroundedGrandTotal: 151200, + roundOff: 0, + grandTotal: 151200, + amountInWords: 'Rupees One Lakh Fifty One Thousand Two Hundred Only', + notes: 'Thank you for your business! Payment due within 15 days.', +}; + +function extractInvoiceData(raw: any): { invoice: InvoiceData; isRealData: boolean } { + if (!raw) return { invoice: DEFAULT_PREVIEW_INVOICE, isRealData: false }; + + const isValid = (obj: any) => Boolean(obj && (obj.invoiceNo || obj.party || obj.items)); + + if (isValid(raw)) return { invoice: normalizeInvoice(raw), isRealData: true }; + if (isValid(raw.result)) return { invoice: normalizeInvoice(raw.result), isRealData: true }; + if (isValid(raw.output)) return { invoice: normalizeInvoice(raw.output), isRealData: true }; + if (isValid(raw.data)) return { invoice: normalizeInvoice(raw.data), isRealData: true }; + + if (Array.isArray(raw.content)) { + for (const item of raw.content) { + if (item.type === 'text' && typeof item.text === 'string') { + try { + const parsed = JSON.parse(item.text); + if (isValid(parsed)) return { invoice: normalizeInvoice(parsed), isRealData: true }; + } catch (e) {} + } + } + } + + if (typeof raw === 'string') { + try { + const parsed = JSON.parse(raw); + if (isValid(parsed)) return { invoice: normalizeInvoice(parsed), isRealData: true }; + } catch (e) {} + } + + return { invoice: DEFAULT_PREVIEW_INVOICE, isRealData: false }; +} + +function normalizeInvoice(raw: any): InvoiceData { + return { + ...DEFAULT_PREVIEW_INVOICE, + ...raw, + party: { ...DEFAULT_PREVIEW_INVOICE.party, ...(raw.party || {}) }, + seller: { ...DEFAULT_PREVIEW_INVOICE.seller, ...(raw.seller || {}) }, + bankDetails: { ...DEFAULT_PREVIEW_INVOICE.bankDetails, ...(raw.bankDetails || {}) }, + items: Array.isArray(raw.items) && raw.items.length > 0 ? raw.items : DEFAULT_PREVIEW_INVOICE.items, + hsnSummary: Array.isArray(raw.hsnSummary) && raw.hsnSummary.length > 0 ? raw.hsnSummary : DEFAULT_PREVIEW_INVOICE.hsnSummary, + }; +} + +export default function InvoiceViewerWidget() { + const theme = useTheme(); + const isDark = theme === 'dark'; + const { getToolOutput } = useWidgetSDK(); + + const rawOutput = getToolOutput(); + const { invoice, isRealData } = extractInvoiceData(rawOutput); + + const fmt = (val: number = 0) => + val.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + + const handlePrintPDF = () => { + const printWin = window.open('', '_blank'); + if (!printWin) return; + printWin.document.write(` + + + + GST Tax Invoice - ${invoice.invoiceNo} + + + +
TAX INVOICE - ${invoice.seller.name}
+ + + + + + + + + +
+ Seller:
${invoice.seller.name}
${invoice.seller.address}
GSTIN: ${invoice.seller.gstin} +
+ Buyer:
${invoice.party.name}
${invoice.party.address}
GSTIN: ${invoice.party.gstin} +
Invoice No: ${invoice.invoiceNo}Date: ${invoice.invoiceDate}
+ + + + + + + + + + + + + + ${(invoice.items || []).map((item, idx) => ` + + + + + + + + + `).join('')} + +
SlItem DescriptionHSNQtyRateLine Total
${idx + 1}${item.description}${item.hsn}${item.qty} ${item.unit || 'Kgs'}₹ ${fmt(item.rate)}₹ ${fmt(item.lineTotal)}
+ +
Grand Total: ₹ ${fmt(invoice.grandTotal)}
+
Amount in words: ${invoice.amountInWords}
+ + + + `); + printWin.document.close(); + }; + + const bgColor = isDark ? '#0f172a' : '#f8fafc'; + const cardBg = isDark ? '#1e293b' : '#ffffff'; + const cardBorder = isDark ? '1px solid #334155' : '1px solid #e2e8f0'; + const textColor = isDark ? '#f8fafc' : '#0f172a'; + const mutedText = isDark ? '#94a3b8' : '#64748b'; + + return ( +
+ {/* Top Banner Card */} +
+
+
+ + + TAX INVOICE + + + #{invoice.invoiceNo} + +
+
+ Issued on {invoice.invoiceDate} • Payment Mode: {invoice.paymentTerms || 'Credit'} +
+
+ + +
+ + {/* Seller & Customer 2-Column Grid */} +
+ {/* Seller Card */} +
+
+ + + Seller (Billed From) + +
+
+ {invoice.seller.name} +
+
+ {invoice.seller.address} +
+
+ GSTIN: {invoice.seller.gstin} +
+
+ Phone: {invoice.seller.phone} +
+
+ + {/* Buyer Card */} +
+
+ + + Buyer (Billed To) + +
+
+ {invoice.party.name} +
+
+ {invoice.party.address} +
+
+ GSTIN: {invoice.party.gstin} +
+
+ State: {invoice.party.state} ({invoice.party.stateCode}) +
+
+
+ + {/* Itemized Goods Table Card */} +
+ + + + + + + + + + + + + + {(invoice.items || []).map((item, idx) => ( + + + + + + + + + + ))} + +
#Item DescriptionHSNQtyRateGST %Line Total
+ {idx + 1} + + {item.description} + + {item.hsn} + + {item.qty} {item.unit || 'Kgs'} + + ₹ {fmt(item.rate)} + + + {item.taxRatePct}% + + + ₹ {fmt(item.lineTotal)} +
+
+ + {/* Summary & Totals Bottom Card */} +
+ {/* Notes & Bank Info */} +
+
+ Amount Chargeable (In Words) +
+
+ {invoice.amountInWords} +
+ +
+ Bank Account Details +
+
+
Bank Name: {invoice.bankDetails?.bankName || 'State Bank of India'}
+
A/c No: {invoice.bankDetails?.accountNo || '39847192834'}
+
IFS Code: {invoice.bankDetails?.ifsc || 'SBIN0001234'}
+
+
+ + {/* Calculation Totals */} +
+
+ Subtotal Gross: + ₹ {fmt(invoice.subtotalGross)} +
+ +
+ Total Taxable Value: + ₹ {fmt(invoice.totalTaxableValue)} +
+ + {!invoice.isInterstate ? ( + <> +
+ CGST Tax Amount: + ₹ {fmt(invoice.totalCGST)} +
+
+ SGST Tax Amount: + ₹ {fmt(invoice.totalSGST)} +
+ + ) : ( +
+ IGST Tax Amount: + ₹ {fmt(invoice.totalIGST)} +
+ )} + +
+ Grand Total: + + ₹ {fmt(invoice.grandTotal)} + +
+
+
+
+ ); +} diff --git a/sample-apps/alphatex-mcp/src/widgets/next-env.d.ts b/sample-apps/alphatex-mcp/src/widgets/next-env.d.ts new file mode 100644 index 000000000..40c3d6809 --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/widgets/next.config.js b/sample-apps/alphatex-mcp/src/widgets/next.config.js new file mode 100644 index 000000000..f35620cee --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/widgets/package-lock.json b/sample-apps/alphatex-mcp/src/widgets/package-lock.json new file mode 100644 index 000000000..0e4ac6852 --- /dev/null +++ b/sample-apps/alphatex-mcp/src/widgets/package-lock.json @@ -0,0 +1,2974 @@ +{ + "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/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/@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", + "peer": true, + "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", + "peer": true, + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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/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/@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/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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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/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/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", + "peer": true, + "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", + "peer": true, + "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/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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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/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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/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/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", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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", + "peer": true + }, + "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/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true + }, + "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/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", + "peer": true, + "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/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "peer": true + }, + "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.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.6.tgz", + "integrity": "sha512-HwMtbJjMw8rC8dUTwCNilHJD+fxTeKM3JV1eprSmTjS41qwXSSt6exJXgyPK1QOu0jB9eDYLESRDkB3qaT3jnw==", + "license": "MIT", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true + }, + "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/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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "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", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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/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/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", + "peer": true, + "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", + "peer": true, + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "peer": true + }, + "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/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "peer": true, + "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", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "peer": true, + "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/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/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", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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==", + "dev": true, + "license": "MIT" + }, + "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", + "peer": true, + "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", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "peer": 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", + "peer": true, + "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", + "peer": true, + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/sample-apps/alphatex-mcp/src/widgets/package.json b/sample-apps/alphatex-mcp/src/widgets/package.json new file mode 100644 index 000000000..1e97ecf3c --- /dev/null +++ b/sample-apps/alphatex-mcp/src/widgets/package.json @@ -0,0 +1,31 @@ +{ + "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": { + "next": "^14.2.5", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@nitrostack/widgets": "^1", + "@modelcontextprotocol/ext-apps": ">=0.1.0", + "mapbox-gl": "^3.0.1", + "framer-motion": "^10.16.16", + "lucide-react": "^0.294.0" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/mapbox-gl": "^3.0.0", + "typescript": "^5", + "tailwindcss": "^3.4.0", + "postcss": "^8.4.32", + "autoprefixer": "^10.4.16" + } +} \ No newline at end of file diff --git a/sample-apps/alphatex-mcp/src/widgets/tsconfig.json b/sample-apps/alphatex-mcp/src/widgets/tsconfig.json new file mode 100644 index 000000000..2c0ad665d --- /dev/null +++ b/sample-apps/alphatex-mcp/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/alphatex-mcp/src/widgets/widget-manifest.json b/sample-apps/alphatex-mcp/src/widgets/widget-manifest.json new file mode 100644 index 000000000..caaea3c3f --- /dev/null +++ b/sample-apps/alphatex-mcp/src/widgets/widget-manifest.json @@ -0,0 +1,94 @@ +{ + "version": "1.0.0", + "widgets": [ + { + "uri": "/invoice-viewer", + "name": "AlphaTex GST Invoice Viewer", + "description": "Interactive GST Tax Invoice rendering with tax breakdown, amount in words, and PDF download button.", + "examples": [ + { + "name": "Standard GST Invoice", + "description": "Display calculated GST tax invoice for cotton yarn supply", + "data": { + "invoiceNo": "AT-2026-001", + "invoiceDate": "2026-08-01", + "dueDate": "2026-08-16", + "seller": { + "name": "ALPHATEX ENTERPRISES", + "gstin": "33AAAAA0000A1Z5", + "address": "123 Textile Park, Tirupur, Tamil Nadu - 641601", + "phone": "+91 98765 43210", + "email": "billing@alphatex.com" + }, + "party": { + "name": "Apex Fabrics & Garments Ltd.", + "gstin": "33BBBBB1111B2Z2", + "address": "45 Fashion Highway, Erode, Tamil Nadu - 638001", + "state": "Tamil Nadu", + "stateCode": "33", + "phone": "+91 91234 56789" + }, + "isInterstate": false, + "items": [ + { + "description": "100% Combed Cotton Yarn 30s", + "hsn": "5205", + "qty": 500, + "rate": 280, + "grossAmount": 140000, + "discountPct": 5, + "discountAmount": 7000, + "taxableValue": 133000, + "taxRatePct": 5, + "cgstRate": 2.5, + "cgstAmount": 3325, + "sgstRate": 2.5, + "sgstAmount": 3325, + "igstRate": 0, + "igstAmount": 0, + "totalTax": 6650, + "lineTotal": 139650 + } + ], + "hsnSummary": [ + { + "hsn": "5205", + "taxableValue": 133000, + "taxRatePct": 5, + "cgstAmount": 3325, + "sgstAmount": 3325, + "igstAmount": 0, + "totalTax": 6650 + } + ], + "subtotalGross": 140000, + "totalItemDiscount": 7000, + "totalTaxableValue": 133000, + "invoiceDiscountPct": 0, + "invoiceDiscountAmount": 0, + "freightCharges": 1500, + "packingCharges": 500, + "netTaxableValue": 135000, + "totalCGST": 3325, + "totalSGST": 3325, + "totalIGST": 0, + "totalTaxAmount": 6650, + "unroundedGrandTotal": 141650, + "roundOff": 0, + "grandTotal": 141650, + "amountInWords": "Rupees One Lakh Forty One Thousand Six Hundred Fifty Only", + "notes": "Payment due within 15 days of invoice date." + } + } + ], + "tags": [ + "invoice", + "gst", + "tax", + "billing", + "alphatex" + ] + } + ], + "generatedAt": "2026-08-01T10:00:00.000Z" +} \ No newline at end of file diff --git a/sample-apps/alphatex-mcp/tsconfig.json b/sample-apps/alphatex-mcp/tsconfig.json new file mode 100644 index 000000000..75818287a --- /dev/null +++ b/sample-apps/alphatex-mcp/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "node", + "rootDir": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "src/widgets"] +} \ No newline at end of file