diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..47c015986b54eb13b96023368b1bca8759b91b30 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM node:20-slim + +# Install pnpm +RUN npm install -g pnpm + +WORKDIR /app + +# Copy all package.json files for dependency installation +COPY package.json pnpm-lock.yaml* ./ +COPY apps/simulator/package.json ./apps/simulator/ +COPY apps/worker/package.json ./apps/worker/ + +# Install all dependencies in the monorepo +RUN pnpm install + +# Copy the rest of the monorepo files +COPY . . + +# Build the simulator service +RUN cd apps/simulator && pnpm install && ./node_modules/.bin/tsc + +# Hugging Face Spaces run on port 7860 by default +EXPOSE 7860 + +# Set environment variables +ENV PORT=7860 +ENV NODE_ENV=production + +# Ensure the correct entry point is used +# Based on the original Dockerfile: CMD ["node", "apps/simulator/dist/apps/simulator/src/index.js"] +# But we need to make sure the path is correct after build +CMD ["node", "apps/simulator/dist/apps/simulator/src/index.js"] diff --git a/README.md b/README.md index f1d04b6a215ba0cc5120aac26de17dd9a8492223..99bf1c9a7b3fb3560fde4d013a40c263dbde5def 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,396 @@ +# Workflow Factor OS + +> **Production-Grade AI Workflow Orchestrator for n8n** +> Built on GitHub + Cloudflare Workers Free Plan Architecture + +--- + +## What Is This? + +Workflow Factor OS is a **production-grade AI Workflow Engineering Platform** that designs, validates, simulates, deploys, and safely activates n8n workflows with professional engineering quality. + +This is **not** a simple JSON generator or prompt-to-workflow toy. + +It behaves like: +- 🏗️ A **workflow engineering team** +- 🖥️ A **workflow operating system** +- 🎛️ An **AI orchestration platform** + +--- + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ USER / FRONTEND │ +└─────────────────────┬───────────────────────────────────────────────┘ + │ HTTPS +┌─────────────────────▼───────────────────────────────────────────────┐ +│ CLOUDFLARE WORKER (apps/worker) │ +│ • Thin API Gateway + Orchestrator │ +│ • Auth, Rate Limiting, KV State Storage │ +│ • Routes: /generate /validate /simulate /deploy /approve /activate │ +│ • CPU budget: 10ms (Free Plan) │ +└─────────────────────┬───────────────────────────────────────────────┘ + │ Internal API (X-Internal-Secret) +┌─────────────────────▼───────────────────────────────────────────────┐ +│ SIMULATOR SERVICE (apps/simulator) — External Node.js/Express │ +│ Heavy CPU work offloaded here: │ +│ • Intent Interpreter Agent (GPT-4o) │ +│ • Workflow Planner Agent (GPT-4o) │ +│ • Graph Engine (IR Layer) (GPT-4o) │ +│ • Workflow Compiler (GPT-4o) │ +│ • Validation Engine (multi-stage) │ +│ • Dry Run Simulator (GPT-4o + chaos testing) │ +│ • Credential Intelligence (n8n API scan) │ +│ • Quality Scorer (multi-dimension scoring) │ +└─────────────────────┬───────────────────────────────────────────────┘ + │ n8n REST API +┌─────────────────────▼───────────────────────────────────────────────┐ +│ n8n INSTANCE │ +│ • Workflow storage (deployed but NOT activated) │ +│ • Credential management │ +│ • Workflow activation (only after human approval) │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Workflow Lifecycle + +``` +User Request + │ + ▼ +[1] Intent Interpretation → classify type, integrations, risk + │ + ▼ +[2] Architecture Planning → layered design, risk analysis + │ + ▼ +[3] Graph IR Generation → internal WorkflowGraph (source of truth) + │ + ▼ +[4] Compile to n8n JSON → valid, schema-compliant n8n workflow + │ + ▼ +[5] Multi-Stage Validation → schema, credentials, graph, expressions, reliability + │ + ▼ +[6] Dry-Run Simulation → mock data, chaos tests, failure prediction + │ + ▼ +[7] Deploy to n8n → active: false (NEVER auto-activate) + │ + ▼ +[8] Credential Check → verify all required credentials exist + │ + ▼ +[9] Human Approval Request → reviewer sees: architecture, risks, scores + │ + ▼ +[10] Human Decision → approve / reject / modify + │ + ▼ +[11] Activation Engine → final validation + activate via n8n API + │ + ▼ +[12] Audit Log → full trail of every lifecycle event +``` + +--- + +## Repository Structure + +``` +workflow-factor-os/ +│ +├── apps/ +│ ├── worker/ # Cloudflare Worker (API Gateway) +│ │ └── src/ +│ │ ├── index.ts # Hono app entry point +│ │ ├── routes/ # generate, validate, simulate, deploy, approval, activate, reports, health +│ │ ├── middleware/ # auth, rateLimit, errorHandler +│ │ ├── types/ # env.ts, workflow.ts (all shared types) +│ │ └── utils/ # audit.ts, ids.ts +│ │ +│ └── simulator/ # External Compute Service (Node.js/Express) +│ └── src/ +│ ├── index.ts # Express app entry point +│ ├── routes/ # generate, validate, simulate +│ ├── middleware/ # internalAuth +│ ├── agents/ # intentInterpreter, workflowPlanner, graphEngine, compiler, validator, simulator, credentialIntelligence +│ ├── services/ # mockDataGenerator, qualityScorer +│ ├── prompts/ # intentInterpreter, workflowPlanner, graphEngine, compiler, simulator +│ ├── knowledge/ # nodeRegistry (real n8n node schemas) +│ └── types/ # workflow types (re-export) +│ +├── core/ # Domain core modules (extensible) +│ ├── interpreter/ # Intent Interpreter logic +│ ├── planner/ # Architecture Planner logic +│ ├── graph/ # Graph IR Engine +│ ├── compiler/ # Workflow Compiler +│ ├── validator/ # Validation Engine +│ ├── simulator/ # Dry Run Simulator +│ ├── deployer/ # Deployment Engine +│ ├── credentials/ # Credential Intelligence +│ ├── approval/ # Human Approval System +│ └── activation/ # Activation Engine +│ +├── knowledge/ +│ ├── n8n-schema/ # n8n JSON schema definitions +│ ├── node-definitions/ # Extracted node registry +│ ├── workflow-patterns/ # Retry, webhook safety, AI, error handling patterns +│ ├── anti-patterns/ # Known bad patterns to avoid +│ └── execution-rules/ # n8n runtime rules +│ +├── integrations/ +│ ├── n8n-client/ # Typed n8n REST API client +│ ├── llm-providers/ # Provider-agnostic LLM abstraction +│ ├── database/ # External DB client (future) +│ └── queue/ # External queue client (future) +│ +├── validation/ +│ ├── schema/ # Zod schemas for all types +│ ├── graph/ # Graph validation utilities +│ ├── credentials/ # Credential validation +│ ├── expressions/ # Expression safety analysis +│ └── reliability/ # Retry, fallback, monitoring checks +│ +├── observability/ +│ ├── logging/ # Pino structured logger +│ ├── metrics/ # Metrics collection (future) +│ ├── tracing/ # Distributed tracing (future) +│ └── audit/ # Audit trail +│ +├── config/ +│ ├── prompts/ # Central prompt registry +│ ├── env/ # .env.example +│ └── n8n/ # n8n API endpoint config +│ +├── scripts/ +│ ├── build-knowledge-base.js # Extract n8n node schemas +│ └── validate-env.js # Pre-deploy env validation +│ +└── docs/ # Architecture diagrams, API docs +``` + --- -title: Workflow Factor Os Simulator -emoji: ⚡ -colorFrom: purple -colorTo: indigo -sdk: docker -pinned: false + +## API Endpoints + +### Cloudflare Worker (Public API) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/health` | Health check (public) | +| `POST` | `/api/workflows/generate` | Full pipeline: intent → plan → graph → compile → validate → simulate | +| `POST` | `/api/workflows/validate` | Re-run validation on existing job | +| `POST` | `/api/workflows/simulate` | Re-run dry-run simulation | +| `POST` | `/api/workflows/deploy` | Deploy to n8n (active: false) | +| `POST` | `/api/workflows/request-approval` | Create human approval request | +| `GET` | `/api/workflows/approve/:jobId` | Get approval request details | +| `POST` | `/api/workflows/approve` | Submit approval decision | +| `POST` | `/api/workflows/activate` | Activate approved workflow | +| `GET` | `/api/reports/workflow/:id` | Full job report | +| `GET` | `/api/reports/validation/:id` | Validation report | +| `GET` | `/api/reports/simulation/:id` | Simulation report | +| `GET` | `/api/reports/credential/:id` | Credential analysis | +| `GET` | `/api/reports/audit/:id` | Full audit trail | + +### Simulator Service (Internal — not public) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/health` | Service health | +| `POST` | `/process/generate` | Full AI pipeline | +| `POST` | `/process/validate` | Validation engine | +| `POST` | `/process/simulate` | Dry-run simulation | + --- -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +## Quick Start + +### 1. Prerequisites + +- Node.js >= 18 +- A running n8n instance with API key +- OpenAI API key +- Cloudflare account (free plan OK) + +### 2. Clone and Install + +```bash +git clone https://github.com/pyaesonegtckglay-dotcom/workflow-factor-os.git +cd workflow-factor-os +npm install +``` + +### 3. Configure Simulator Service + +```bash +cp config/env/.env.example apps/simulator/.env +# Edit .env with your values +``` + +### 4. Start Simulator Service + +```bash +npm run dev:simulator +# Runs on http://localhost:3001 +``` + +### 5. Configure & Deploy Cloudflare Worker + +```bash +cd apps/worker + +# Set secrets +wrangler secret put OPENAI_API_KEY +wrangler secret put N8N_API_KEY +wrangler secret put INTERNAL_API_SECRET + +# Create KV namespace +wrangler kv:namespace create "WFO_CACHE" +# Copy the ID into wrangler.toml + +# Update wrangler.toml with your SIMULATOR_URL and N8N_BASE_URL + +# Deploy +npm run deploy +``` + +### 6. Test + +```bash +# Generate a workflow +curl -X POST https://your-worker.workers.dev/api/workflows/generate \ + -H "X-API-Key: your-secret" \ + -H "Content-Type: application/json" \ + -d '{ + "request": "Build a Telegram bot that receives messages, processes them with GPT-4o, and logs results to Google Sheets" + }' + +# Get the jobId from response, then check full report: +curl https://your-worker.workers.dev/api/reports/workflow/ \ + -H "X-API-Key: your-secret" +``` + +--- + +## 12 Core Engineering Systems + +| System | Location | Description | +|--------|----------|-------------| +| Intent Interpreter | `apps/simulator/src/agents/intentInterpreter.ts` | Classifies request, identifies integrations, risk, domain | +| Workflow Planner | `apps/simulator/src/agents/workflowPlanner.ts` | Layered architecture design, risk analysis | +| Graph Engine (IR) | `apps/simulator/src/agents/graphEngine.ts` | WorkflowGraph IR - source of truth | +| Pattern Knowledge Engine | `knowledge/workflow-patterns/` | Retry, webhook safety, AI, error patterns | +| n8n Repository Intelligence | `apps/simulator/src/knowledge/nodeRegistry.ts` | Real n8n node schemas | +| Workflow Compiler | `apps/simulator/src/agents/compiler.ts` | Graph IR → valid n8n JSON | +| Validation Engine | `apps/simulator/src/agents/validator.ts` | 5-stage validation | +| Dry Run Simulation | `apps/simulator/src/agents/simulator.ts` | Mock execution + chaos testing | +| Deployment Engine | `apps/worker/src/routes/deploy.ts` | Safe n8n deployment (never auto-activate) | +| Credential Intelligence | `apps/simulator/src/agents/credentialIntelligence.ts` | Credential scan + missing detection | +| Human Approval System | `apps/worker/src/routes/approval.ts` | Mandatory human gate before activation | +| Activation Engine | `apps/worker/src/routes/activate.ts` | Final safety checks + n8n activation | + +--- + +## Engineering Philosophy + +### NEVER: +- ❌ Instantly activate workflows +- ❌ Skip validation +- ❌ Skip dry-run simulation +- ❌ Assume credentials exist +- ❌ Hallucinate node parameters +- ❌ Generate monolithic spaghetti workflows + +### ALWAYS: +- ✅ Validate before deploying +- ✅ Simulate before deploying +- ✅ Check credentials +- ✅ Require human approval +- ✅ Use modular workflow design +- ✅ Think about retries and fallbacks +- ✅ Think about observability +- ✅ Use optional chaining in expressions + +--- + +## Cloudflare Workers Free Plan Adaptations + +Per the architecture addendum, the CF Worker is a **thin API gateway** only: + +| Component | Free Plan Approach | +|-----------|-------------------| +| Intent Interpreter | Offloaded to Simulator service | +| Workflow Planner | Offloaded to Simulator service | +| Graph Engine | Offloaded to Simulator service | +| Compiler | Offloaded to Simulator service | +| Validation Engine | Offloaded to Simulator service | +| Dry Run Simulator | Offloaded to Simulator service | +| State Storage | Workers KV (job records, 7-day TTL) | +| Credential Check | Worker calls n8n API directly | +| Deployment | Worker calls n8n API directly | +| Activation | Worker calls n8n API directly | +| Auth & Rate Limiting | Worker handles (minimal CPU) | + +--- + +## Workflow Quality Scoring + +Every generated workflow is scored across 9 dimensions: + +| Dimension | Weight | What It Measures | +|-----------|--------|-----------------| +| Schema Correctness | 25% | Valid n8n JSON structure | +| Credential Readiness | 20% | All required credentials present | +| Graph Validity | 20% | No orphans, proper connections | +| Expression Safety | 15% | Null-safe expressions | +| Reliability | 20% | Retry policies, fallbacks, monitoring | + +**Deployment Readiness:** +- 🟢 85-100: Low risk — safe to deploy +- 🟡 65-84: Medium risk — review warnings +- 🟠 40-64: High risk — fix issues first +- 🔴 0-39: Critical — do not deploy + +--- + +## Workflow Lifecycle States + +``` +draft → planned → generated → validated → simulated → +deployed → awaiting_approval → approved/rejected → activated → monitored → deprecated +``` + +--- + +## Security + +- All API endpoints protected by `X-API-Key` header +- Worker ↔ Simulator communication uses shared `INTERNAL_API_SECRET` +- All secrets stored as Cloudflare Worker Secrets (never in wrangler.toml) +- Credentials never logged or exposed in responses +- Rate limiting: 20 requests/minute per IP via KV + +--- + +## Future Roadmap + +- [ ] Multi-user / RBAC support +- [ ] Workflow template marketplace +- [ ] AI workflow memory (remember successful patterns) +- [ ] Autonomous workflow optimization +- [ ] Self-healing workflows +- [ ] Runtime anomaly detection +- [ ] Web UI for human approval dashboard +- [ ] D1 database for persistent job storage (beyond KV) + +--- + +## License + +MIT diff --git a/apps/simulator/.dockerignore b/apps/simulator/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..963f7427b5eae00dc6949e461b29a0ee8582578c --- /dev/null +++ b/apps/simulator/.dockerignore @@ -0,0 +1,9 @@ +node_modules +dist +.env +.git +.github +.vscode +.DS_Store +apps/*/node_modules +apps/*/dist diff --git a/apps/simulator/Dockerfile b/apps/simulator/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..dca0f29ddc9bfc5f2c88b3b59848d371d625c0cd --- /dev/null +++ b/apps/simulator/Dockerfile @@ -0,0 +1,25 @@ +FROM node:20-slim + +WORKDIR /app + +# Install pnpm +RUN npm install -g pnpm + +# Copy all package.json files for dependency installation +COPY package.json pnpm-lock.yaml* ./ +COPY apps/simulator/package.json ./apps/simulator/ +COPY apps/worker/package.json ./apps/worker/ + +# Install all dependencies in the monorepo +RUN pnpm install + +# Copy the rest of the monorepo files +COPY . . + +# Build the simulator service +RUN cd apps/simulator && npm install && ./node_modules/.bin/tsc + +EXPOSE 7860 + +# Ensure the correct entry point is used +CMD ["node", "apps/simulator/dist/apps/simulator/src/index.js"] diff --git a/apps/simulator/README.md b/apps/simulator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..74580ecade6cc920885a65bd3c8c57b4d956eea0 --- /dev/null +++ b/apps/simulator/README.md @@ -0,0 +1,36 @@ +# Workflow Factor OS - Simulator Service + +This is the Simulator Service component of the Workflow Factor OS monorepo, deployed to Hugging Face Spaces. + +## Environment Variables + +The following environment variables **MUST** be set in your Hugging Face Space settings: + +* `LLM_GATEWAY_URL`: The URL of your custom LLM gateway (e.g., `https://llm-routing-system.pyaesone-n8n.workers.dev/v1/chat/completions`) +* `LLM_API_KEY`: Your API key for the LLM gateway. This will be used as the primary LLM key. +* `N8N_BASE_URL`: The base URL of your n8n instance (e.g., `https://pyae1994-n8n-aiven-v1.hf.space/`) +* `N8N_API_KEY`: Your API key for the n8n instance. +* `INTERNAL_API_SECRET`: A shared secret between the Cloudflare Worker and this Simulator service for internal API authentication (e.g., `super-secret-PSA`). + +## Deployment + +This service is designed to be deployed as a Docker image to Hugging Face Spaces. The `Dockerfile` in this directory handles the build process. + +### Build and Run Locally (for testing) + +To build and run this service locally using Docker, navigate to the `apps/simulator` directory and execute: + +```bash +docker build -t wfo-simulator . +docker run -p 7860:7860 -e LLM_GATEWAY_URL="your_llm_gateway_url" -e LLM_API_KEY="your_llm_api_key" -e N8N_BASE_URL="your_n8n_base_url" -e N8N_API_KEY="your_n8n_api_key" -e INTERNAL_API_SECRET="your_internal_api_secret" wfo-simulator +``` + +### Hugging Face Spaces Deployment + +1. Create a new Space on Hugging Face. +2. Select "Docker" as the Space SDK. +3. Upload the contents of the `apps/simulator` directory (including the `Dockerfile` and `.dockerignore`) to your Hugging Face Space repository. +4. Configure the environment variables listed above in the Space settings. +5. Hugging Face Spaces will automatically build and deploy your Docker image. + +**Note:** The service exposes port `7860` as required by Hugging Face Spaces for custom Docker applications. diff --git a/apps/simulator/package.json b/apps/simulator/package.json new file mode 100644 index 0000000000000000000000000000000000000000..1d449964782bb7dcef76101fb723ce60f1e17d9b --- /dev/null +++ b/apps/simulator/package.json @@ -0,0 +1,31 @@ +{ + "name": "@wfo/simulator", + "version": "1.0.0", + "description": "External Compute Service - Heavy AI orchestration logic (Intent, Planner, Graph, Compiler, Validator, Simulator)", + "private": true, + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "lint": "eslint src --ext .ts" + }, + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.0", + "express": "^4.18.0", + "express-rate-limit": "^7.2.0", + "helmet": "^7.1.0", + "module-alias": "^2.3.4", + "openai": "^4.28.0", + "pino": "^8.17.0", + "pino-pretty": "^10.3.0", + "zod": "^3.22.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^20.11.0", + "tsx": "^4.7.0", + "typescript": "^5.3.0" + } +} diff --git a/apps/simulator/pnpm-lock.yaml b/apps/simulator/pnpm-lock.yaml new file mode 100644 index 0000000000000000000000000000000000000000..518cf4c58925ccbbffd072be24a728e768790954 --- /dev/null +++ b/apps/simulator/pnpm-lock.yaml @@ -0,0 +1,1492 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + cors: + specifier: ^2.8.5 + version: 2.8.6 + dotenv: + specifier: ^16.4.0 + version: 16.6.1 + express: + specifier: ^4.18.0 + version: 4.22.1 + express-rate-limit: + specifier: ^7.2.0 + version: 7.5.1(express@4.22.1) + helmet: + specifier: ^7.1.0 + version: 7.2.0 + openai: + specifier: ^4.28.0 + version: 4.104.0(zod@3.25.76) + pino: + specifier: ^8.17.0 + version: 8.21.0 + pino-pretty: + specifier: ^10.3.0 + version: 10.3.1 + zod: + specifier: ^3.22.0 + version: 3.25.76 + devDependencies: + '@types/cors': + specifier: ^2.8.17 + version: 2.8.19 + '@types/express': + specifier: ^4.17.21 + version: 4.17.25 + '@types/node': + specifier: ^20.11.0 + version: 20.19.40 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + +packages: + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + + '@types/express@4.17.25': + resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + + '@types/node@20.19.40': + resolution: {integrity: sha512-xxx6M2IpSTnnKcR0cMvIiohkiCx20/oRPtWGbenFygKCGl3zqUzdNjQ/1V4solq1LU+dgv0nQzeGOuqkqZGg0Q==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + body-parser@1.20.5: + resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + engines: {node: '>= 0.10.0'} + + fast-copy@3.0.2: + resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + helmet@7.2.0: + resolution: {integrity: sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==} + engines: {node: '>=16.0.0'} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + openai@4.104.0: + resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + + pino-abstract-transport@1.2.0: + resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} + + pino-pretty@10.3.1: + resolution: {integrity: sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==} + hasBin: true + + pino-std-serializers@6.2.2: + resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + + pino@8.21.0: + resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} + hasBin: true + + process-warning@3.0.0: + resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + sonic-boom@3.8.1: + resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + thread-stream@2.7.0: + resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.19.40 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.19.40 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 20.19.40 + + '@types/express-serve-static-core@4.19.8': + dependencies: + '@types/node': 20.19.40 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@4.17.25': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.8 + '@types/qs': 6.15.1 + '@types/serve-static': 1.15.10 + + '@types/http-errors@2.0.5': {} + + '@types/mime@1.3.5': {} + + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 20.19.40 + form-data: 4.0.5 + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + + '@types/node@20.19.40': + dependencies: + undici-types: 6.21.0 + + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@0.17.6': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.19.40 + + '@types/send@1.2.1': + dependencies: + '@types/node': 20.19.40 + + '@types/serve-static@1.15.10': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 20.19.40 + '@types/send': 0.17.6 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + array-flatten@1.1.1: {} + + asynckit@0.4.0: {} + + atomic-sleep@1.0.0: {} + + base64-js@1.5.1: {} + + body-parser@1.20.5: + 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 + transitivePeerDependencies: + - supports-color + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + dateformat@4.6.3: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + events@3.3.0: {} + + express-rate-limit@7.5.1(express@4.22.1): + dependencies: + express: 4.22.1 + + express@4.22.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.5 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.13 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-copy@3.0.2: {} + + fast-redact@3.5.0: {} + + fast-safe-stringify@2.1.1: {} + + finalhandler@1.3.2: + 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 + transitivePeerDependencies: + - supports-color + + form-data-encoder@1.7.2: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + helmet@7.2.0: {} + + help-me@5.0.0: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + joycon@3.1.1: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + minimist@1.2.8: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + negotiator@0.6.3: {} + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openai@4.104.0(zod@3.25.76): + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + zod: 3.25.76 + transitivePeerDependencies: + - encoding + + parseurl@1.3.3: {} + + path-to-regexp@0.1.13: {} + + pino-abstract-transport@1.2.0: + dependencies: + readable-stream: 4.7.0 + split2: 4.2.0 + + pino-pretty@10.3.1: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 3.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.2.0 + pump: 3.0.4 + readable-stream: 4.7.0 + secure-json-parse: 2.7.0 + sonic-boom: 3.8.1 + strip-json-comments: 3.1.1 + + pino-std-serializers@6.2.2: {} + + pino@8.21.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.2.0 + pino-std-serializers: 6.2.2 + process-warning: 3.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 3.8.1 + thread-stream: 2.7.0 + + process-warning@3.0.0: {} + + process@0.11.10: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + qs@6.14.2: + dependencies: + side-channel: 1.1.0 + + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + + quick-format-unescaped@4.0.4: {} + + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + real-require@0.2.0: {} + + resolve-pkg-maps@1.0.0: {} + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + secure-json-parse@2.7.0: {} + + send@0.19.2: + 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 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + 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 + + sonic-boom@3.8.1: + dependencies: + atomic-sleep: 1.0.0 + + split2@4.2.0: {} + + statuses@2.0.2: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-json-comments@3.1.1: {} + + thread-stream@2.7.0: + dependencies: + real-require: 0.2.0 + + toidentifier@1.0.1: {} + + tr46@0.0.3: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typescript@5.9.3: {} + + undici-types@5.26.5: {} + + undici-types@6.21.0: {} + + unpipe@1.0.0: {} + + utils-merge@1.0.1: {} + + vary@1.1.2: {} + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + wrappy@1.0.2: {} + + zod@3.25.76: {} diff --git a/apps/simulator/pnpm-workspace.yaml b/apps/simulator/pnpm-workspace.yaml new file mode 100644 index 0000000000000000000000000000000000000000..00f6fc47030edf977a2e8d0f76a9ff3c4db780f2 --- /dev/null +++ b/apps/simulator/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: set this to true or false diff --git a/apps/simulator/src/agents/compiler.ts b/apps/simulator/src/agents/compiler.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ee59f849bf73ce329270dc8f5a810cdd9c6d0f8 --- /dev/null +++ b/apps/simulator/src/agents/compiler.ts @@ -0,0 +1,99 @@ +/** + * Workflow Compiler Agent — UPGRADED + * Converts WorkflowGraph (IR) → valid n8n JSON + * Schema-aware, version-aware, credential-aware + * Provider-agnostic LLM Gateway — NO direct OpenAI dependency + * STRICT: Only uses nodes from registry. Zero hallucinated params. + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { WorkflowGraph, WorkflowIntent, N8nWorkflow } from '../types/workflow'; +import { COMPILER_PROMPT } from '../prompts/compiler'; +import { N8N_NODE_REGISTRY, isValidNodeType } from '../knowledge/nodeRegistry'; + +export class WorkflowCompiler { + private llm: LLMGateway; + + constructor(llm: LLMGateway) { + this.llm = llm; + } + + async compile(graph: WorkflowGraph, intent: WorkflowIntent): Promise { + // Build node schema context from registry (ONLY nodes used in graph) + const nodeTypes = [...new Set(graph.nodes.map((n) => n.n8nNodeType))]; + const validNodeTypes = nodeTypes.filter(isValidNodeType); + const nodeSchemas = validNodeTypes + .map((type) => N8N_NODE_REGISTRY[type]) + .filter(Boolean) + .map((s) => JSON.stringify(s, null, 2)) + .join('\n\n'); + + const workflow = await this.llm.completeJSON([ + { role: 'system', content: COMPILER_PROMPT }, + { + role: 'user', + content: `Compile this WorkflowGraph to a valid n8n JSON workflow. + +GRAPH: +${JSON.stringify(graph, null, 2)} + +INTENT: +${JSON.stringify(intent, null, 2)} + +REGISTERED NODE SCHEMAS (use ONLY these — parameters MUST match schema): +${nodeSchemas || '(No pre-built schemas — use standard n8n defaults)'} + +CRITICAL COMPILATION RULES: +1. active MUST be false — NEVER true +2. All node IDs must be unique UUID strings (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) +3. connections MUST wire ALL graph edges correctly +4. NEVER invent parameters not in the node schema above +5. Every node MUST have real expressions for dynamic fields — NO static placeholder text +6. Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 on all critical/external nodes +7. Add meaningful node notes explaining what each node does +8. USE these expression patterns: + - Input from trigger: {{$json?.body?.fieldName ?? $json?.fieldName ?? ""}} + - Previous node: {{$node["NodeName"].json?.field ?? ""}} + - Array item: {{$json?.items?.[0]?.value ?? ""}} + - Conditional: {{$json?.status === "active" ? "yes" : "no"}} +9. SET nodes MUST have explicit field mappings — NOT empty values array +10. IF nodes MUST have real boolean conditions referencing actual fields +11. CODE nodes MUST contain real JavaScript logic — NOT placeholder comments +12. AI Agent nodes MUST include system message AND dynamic user message from previous node data +13. Return ONLY complete, importable n8n JSON workflow`, + }, + ], { + temperature: 0.0, + retries: 3, + }); + + return this.ensureSafeDefaults(workflow); + } + + /** + * Enforce safety defaults regardless of LLM output + * CRITICAL: active = false, proper settings, error handling + */ + private ensureSafeDefaults(workflow: N8nWorkflow): N8nWorkflow { + return { + ...workflow, + active: false, // NEVER activate on compile — ABSOLUTE safety rule + settings: { + callerPolicy: 'workflowsFromSameOwner', + errorWorkflow: workflow.settings?.errorWorkflow, + timezone: workflow.settings?.timezone ?? 'UTC', + ...workflow.settings, + // Force these even if LLM overrides: + executionOrder: 'v1', + saveManualExecutions: true, + }, + nodes: workflow.nodes.map((node) => ({ + ...node, + onError: node.onError ?? 'continueErrorOutput', + // Ensure critical external nodes have retry + retryOnFail: node.retryOnFail ?? false, + maxTries: node.maxTries ?? 3, + waitBetweenTries: node.waitBetweenTries ?? 1000, + })), + }; + } +} diff --git a/apps/simulator/src/agents/credentialIntelligence.ts b/apps/simulator/src/agents/credentialIntelligence.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b748d02e7e1d9216f186fa7325c4fd8950423aa --- /dev/null +++ b/apps/simulator/src/agents/credentialIntelligence.ts @@ -0,0 +1,177 @@ +/** + * Credential Intelligence System — UPGRADED + * Provider-agnostic, uses Node Registry for credential mapping + * Inspects workflow to identify required credentials + * Scans existing n8n credentials and maps types + * Detects missing credentials and blocks activation if needed + */ +import type { + N8nWorkflow, + CredentialAnalysis, + RequiredCredential, + AvailableCredential, + MissingCredential, +} from '../types/workflow'; +import { N8N_NODE_REGISTRY } from '../knowledge/nodeRegistry'; + +// Extended node → credential map (derived from registry + additional mappings) +const NODE_CREDENTIAL_MAP: Record = { + 'n8n-nodes-base.telegram': 'telegramApi', + 'n8n-nodes-base.telegramTrigger': 'telegramApi', + 'n8n-nodes-base.openAi': 'openAiApi', + '@n8n/n8n-nodes-langchain.openAi': 'openAiApi', + '@n8n/n8n-nodes-langchain.agent': undefined, // depends on sub-node + '@n8n/n8n-nodes-langchain.lmChatAnthropic': 'anthropicApi', + '@n8n/n8n-nodes-langchain.memoryBufferWindow': undefined, + '@n8n/n8n-nodes-langchain.toolCode': undefined, + 'n8n-nodes-base.gmail': 'googleOAuth2Api', + 'n8n-nodes-base.emailReadImap': 'imap', + 'n8n-nodes-base.googleSheets': 'googleSheetsOAuth2Api', + 'n8n-nodes-base.slack': 'slackApi', + 'n8n-nodes-base.slackTrigger': 'slackOAuth2Api', + 'n8n-nodes-base.airtable': 'airtableTokenApi', + 'n8n-nodes-base.notion': 'notionApi', + 'n8n-nodes-base.notionTrigger': 'notionApi', + 'n8n-nodes-base.github': 'githubApi', + 'n8n-nodes-base.githubTrigger': 'githubApi', + 'n8n-nodes-base.stripe': 'stripeApi', + 'n8n-nodes-base.hubspot': 'hubspotApi', + 'n8n-nodes-base.postgres': 'postgres', + 'n8n-nodes-base.mysql': 'mySql', + 'n8n-nodes-base.mongodb': 'mongoDb', + 'n8n-nodes-base.redis': 'redis', + // No credential needed + 'n8n-nodes-base.httpRequest': undefined, + 'n8n-nodes-base.webhook': undefined, + 'n8n-nodes-base.scheduleTrigger': undefined, + 'n8n-nodes-base.manualTrigger': undefined, + 'n8n-nodes-base.code': undefined, + 'n8n-nodes-base.set': undefined, + 'n8n-nodes-base.if': undefined, + 'n8n-nodes-base.switch': undefined, + 'n8n-nodes-base.merge': undefined, + 'n8n-nodes-base.splitInBatches': undefined, + 'n8n-nodes-base.itemLists': undefined, + 'n8n-nodes-base.filter': undefined, + 'n8n-nodes-base.noOp': undefined, + 'n8n-nodes-base.stopAndError': undefined, + 'n8n-nodes-base.wait': undefined, + 'n8n-nodes-base.respondToWebhook': undefined, + 'n8n-nodes-base.stickyNote': undefined, +}; + +const CREDENTIAL_SETUP_INSTRUCTIONS: Record = { + telegramApi: 'Create a Telegram Bot via @BotFather, copy the bot token, then add "Telegram API" credential in n8n Settings → Credentials.', + openAiApi: 'Generate an API key at https://platform.openai.com/api-keys, then add "OpenAI" credential in n8n.', + anthropicApi: 'Get your API key at https://console.anthropic.com/, then add "Anthropic" credential in n8n.', + googleOAuth2Api: 'Set up a Google Cloud project, enable Gmail API, create OAuth2 credentials, then add "Google OAuth2 API" in n8n.', + googleSheetsOAuth2Api: 'Set up Google Cloud project, enable Sheets API, create OAuth2 credentials, then add "Google Sheets OAuth2 API" in n8n.', + slackApi: 'Create a Slack App at https://api.slack.com/apps with required scopes, then add "Slack API" credential in n8n.', + slackOAuth2Api: 'Create a Slack App with OAuth2, then add "Slack OAuth2 API" credential in n8n.', + airtableTokenApi: 'Generate a Personal Access Token at https://airtable.com/account, then add "Airtable Token API" in n8n.', + notionApi: 'Create an integration at https://www.notion.so/my-integrations, then add "Notion API" credential in n8n.', + githubApi: 'Generate a Personal Access Token at https://github.com/settings/tokens, then add "GitHub API" in n8n.', + stripeApi: 'Get your API key from https://dashboard.stripe.com/apikeys, then add "Stripe API" in n8n.', + hubspotApi: 'Create a Private App in HubSpot settings, copy the access token, then add "HubSpot API" in n8n.', + postgres: 'Provide your PostgreSQL host, port, database, username, and password in "Postgres" credential in n8n.', + mySql: 'Provide your MySQL host, port, database, username, and password in "MySQL" credential in n8n.', + mongoDb: 'Provide your MongoDB connection string in "MongoDB" credential in n8n.', + redis: 'Provide your Redis host, port, and optional password in "Redis" credential in n8n.', + imap: 'Provide your IMAP server host, port, username, and password in "IMAP" credential in n8n.', +}; + +export class CredentialIntelligence { + private n8nBaseUrl: string; + private n8nApiKey: string; + + constructor(n8nBaseUrl: string, n8nApiKey: string) { + this.n8nBaseUrl = n8nBaseUrl; + this.n8nApiKey = n8nApiKey; + } + + async analyse(jobId: string, workflow: N8nWorkflow): Promise { + // ─── 1. Identify required credentials from workflow nodes ────────────── + const required: RequiredCredential[] = []; + const requiredTypes = new Set(); + + workflow.nodes.forEach((node) => { + // Check registry-based credential (more accurate) + const registryDef = N8N_NODE_REGISTRY[node.type]; + const credType = registryDef?.credentialType ?? NODE_CREDENTIAL_MAP[node.type]; + + if (credType !== undefined && credType !== '') { + if (!requiredTypes.has(credType)) { + required.push({ + nodeId: node.id, + nodeName: node.name, + credentialType: credType, + credentialDisplayName: registryDef?.credentialDisplayName, + required: true, + }); + requiredTypes.add(credType); + } + } + + // Also check node.credentials field (explicitly set by compiler) + if (node.credentials) { + Object.keys(node.credentials).forEach((cType) => { + if (!requiredTypes.has(cType)) { + required.push({ + nodeId: node.id, + nodeName: node.name, + credentialType: cType, + required: true, + }); + requiredTypes.add(cType); + } + }); + } + }); + + // ─── 2. Fetch available credentials from n8n ─────────────────────────── + let available: AvailableCredential[] = []; + let n8nReachable = false; + try { + const resp = await fetch(`${this.n8nBaseUrl}/api/v1/credentials`, { + headers: { 'X-N8N-API-KEY': this.n8nApiKey }, + signal: AbortSignal.timeout(8000), + }); + if (resp.ok) { + const data = await resp.json() as { data: AvailableCredential[] }; + available = data.data ?? []; + n8nReachable = true; + } + } catch { + // n8n not accessible — non-fatal, report as warning + } + + const availableTypes = new Set(available.map((c) => c.type)); + + // ─── 3. Identify missing credentials ────────────────────────────────── + const missingTypeMap = new Map(); + required.forEach((req) => { + // Only report as missing if n8n is reachable and credential is not there + if (n8nReachable && !availableTypes.has(req.credentialType)) { + const existing = missingTypeMap.get(req.credentialType) ?? []; + if (!existing.includes(req.nodeName)) existing.push(req.nodeName); + missingTypeMap.set(req.credentialType, existing); + } + }); + + const missing: MissingCredential[] = [...missingTypeMap.entries()].map(([credType, nodeNames]) => ({ + credentialType: credType, + requiredByNodes: nodeNames, + setupInstructions: CREDENTIAL_SETUP_INSTRUCTIONS[credType] ?? `Set up "${credType}" credential in n8n Settings → Credentials.`, + })); + + return { + jobId, + allCredentialsPresent: missing.length === 0, + n8nReachable, + required, + available, + missing, + analysedAt: new Date().toISOString(), + }; + } +} diff --git a/apps/simulator/src/agents/graphEngine.ts b/apps/simulator/src/agents/graphEngine.ts new file mode 100644 index 0000000000000000000000000000000000000000..abcf7ca2f503b84276513766aa5bbf92cb107037 --- /dev/null +++ b/apps/simulator/src/agents/graphEngine.ts @@ -0,0 +1,110 @@ +/** + * Workflow Graph Engine (IR Layer) — UPGRADED + * Builds an internal graph representation BEFORE compiling to n8n JSON + * Provider-agnostic LLM Gateway — NO direct OpenAI dependency + * Validates all node types against the real Node Registry before returning + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { WorkflowIntent, WorkflowArchitecturePlan, WorkflowGraph } from '../types/workflow'; +import { GRAPH_ENGINE_PROMPT } from '../prompts/graphEngine'; +import { isValidNodeType, getRegistryNodeList } from '../knowledge/nodeRegistry'; + +export class WorkflowGraphEngine { + private llm: LLMGateway; + + constructor(llm: LLMGateway) { + this.llm = llm; + } + + async buildGraph( + userRequest: string, + intent: WorkflowIntent, + plan: WorkflowArchitecturePlan, + ): Promise { + const registryList = getRegistryNodeList(); + + const graph = await this.llm.completeJSON([ + { role: 'system', content: GRAPH_ENGINE_PROMPT }, + { + role: 'user', + content: `Build a WorkflowGraph IR for this workflow: + +REQUEST: ${userRequest} + +INTENT: +${JSON.stringify(intent, null, 2)} + +ARCHITECTURE PLAN: +${JSON.stringify(plan, null, 2)} + +AVAILABLE NODE TYPES (USE ONLY THESE — NO OTHERS): +${registryList} + +CRITICAL RULES: +- Every node MUST have n8nNodeType from the list above ONLY +- NEVER invent node types not in the list +- Every non-trigger node MUST have at least one incoming edge +- Every node MUST have meaningful parameters — NO empty nodes +- DataContracts must define what JSON fields flow between nodes +- Use real expressions: {{$json?.field ?? ""}} — NOT placeholder text +- Position nodes in clean left-to-right layout (x increases by 220 per step) +- Return a complete WorkflowGraph JSON`, + }, + ], { + temperature: 0.0, + retries: 3, + }); + + return this.validateAndOptimizeGraph(graph); + } + + /** + * Validates all node types against registry, removes unknown nodes, + * optimises graph structure (orphan removal, position cleanup) + */ + private validateAndOptimizeGraph(graph: WorkflowGraph): WorkflowGraph { + const unknownNodes: string[] = []; + + // Filter out any hallucinated node types + const validNodes = graph.nodes.filter((node) => { + if (!isValidNodeType(node.n8nNodeType)) { + unknownNodes.push(`${node.label} (${node.n8nNodeType})`); + return false; + } + return true; + }); + + if (unknownNodes.length > 0) { + console.warn( + `[GraphEngine] REJECTED ${unknownNodes.length} unknown node type(s): ${unknownNodes.join(', ')}`, + ); + } + + const validNodeIds = new Set(validNodes.map((n) => n.id)); + + // Remove edges that reference removed nodes + const validEdges = graph.edges.filter( + (e) => validNodeIds.has(e.sourceNodeId) && validNodeIds.has(e.targetNodeId), + ); + + // Remove orphaned non-trigger nodes + const optimizedNodes = validNodes.filter((node) => { + const hasIncoming = validEdges.some((e) => e.targetNodeId === node.id); + const hasOutgoing = validEdges.some((e) => e.sourceNodeId === node.id); + const isTrigger = node.layer === 'trigger'; + return isTrigger || hasIncoming || hasOutgoing; + }); + + return { + ...graph, + nodes: optimizedNodes, + edges: validEdges, + metadata: { + ...graph.metadata, + version: '2.0.0', + createdAt: new Date().toISOString(), + unknownNodesRejected: unknownNodes, + }, + }; + } +} diff --git a/apps/simulator/src/agents/intentInterpreter.ts b/apps/simulator/src/agents/intentInterpreter.ts new file mode 100644 index 0000000000000000000000000000000000000000..41af1517c344f9911de7832bc7131115a528ccdf --- /dev/null +++ b/apps/simulator/src/agents/intentInterpreter.ts @@ -0,0 +1,48 @@ +/** + * Intent Interpreter Agent — UPGRADED + * Deeply understands user's workflow request using provider-agnostic LLM Gateway + * NO direct OpenAI dependency + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { WorkflowIntent } from '../types/workflow'; +import { INTENT_INTERPRETER_PROMPT } from '../prompts/intentInterpreter'; + +export class IntentInterpreterAgent { + private llm: LLMGateway; + + constructor(llm: LLMGateway) { + this.llm = llm; + } + + async interpret(userRequest: string): Promise { + const raw = await this.llm.completeJSON>([ + { role: 'system', content: INTENT_INTERPRETER_PROMPT }, + { + role: 'user', + content: `Analyse this workflow request and return structured JSON:\n\n${userRequest}`, + }, + ], { + temperature: 0.1, + retries: 3, + }); + + return this.validateAndNormalize(raw); + } + + private validateAndNormalize(raw: Partial): WorkflowIntent { + return { + workflowType: raw.workflowType ?? 'general', + requiresAI: raw.requiresAI ?? false, + integrations: Array.isArray(raw.integrations) ? raw.integrations : [], + riskLevel: raw.riskLevel ?? 'medium', + requiresHumanApproval: raw.requiresHumanApproval ?? true, + syncVsAsync: raw.syncVsAsync ?? 'sync', + scalingRequirements: raw.scalingRequirements ?? 'low', + identifiedRisks: Array.isArray(raw.identifiedRisks) ? raw.identifiedRisks : [], + estimatedComplexity: raw.estimatedComplexity ?? 'moderate', + domain: raw.domain ?? 'general', + triggerType: raw.triggerType ?? 'webhook', + triggerHint: raw.triggerHint, + }; + } +} diff --git a/apps/simulator/src/agents/simulator.ts b/apps/simulator/src/agents/simulator.ts new file mode 100644 index 0000000000000000000000000000000000000000..9a2d30d748b8897274ad44755bfc21af9a4699fe --- /dev/null +++ b/apps/simulator/src/agents/simulator.ts @@ -0,0 +1,89 @@ +/** + * Dry Run Simulation Engine — UPGRADED + * Simulates workflow execution with mock data BEFORE deployment + * Provider-agnostic LLM Gateway — NO direct OpenAI dependency + * Detects: runtime issues, null references, failure points, chaos scenarios + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { + N8nWorkflow, + WorkflowGraph, + WorkflowArchitecturePlan, + SimulationReport, +} from '../types/workflow'; +import { SIMULATOR_PROMPT } from '../prompts/simulator'; +import { MockDataGenerator } from '../services/mockDataGenerator'; + +export class DryRunSimulator { + private llm: LLMGateway; + private mockDataGen: MockDataGenerator; + + constructor(llm: LLMGateway) { + this.llm = llm; + this.mockDataGen = new MockDataGenerator(llm); + } + + async simulate( + jobId: string, + workflow: N8nWorkflow, + graph: WorkflowGraph, + plan: WorkflowArchitecturePlan, + ): Promise { + // Generate realistic mock data for trigger node + const mockData = await this.mockDataGen.generate(workflow, graph); + + const result = await this.llm.completeJSON>([ + { role: 'system', content: SIMULATOR_PROMPT }, + { + role: 'user', + content: `Simulate the execution of this n8n workflow using the provided mock data. + +WORKFLOW: +${JSON.stringify(workflow, null, 2)} + +GRAPH (IR): +${JSON.stringify(graph, null, 2)} + +ARCHITECTURE PLAN: +${JSON.stringify(plan, null, 2)} + +MOCK INPUT DATA: +${JSON.stringify(mockData, null, 2)} + +Produce a SimulationReport JSON with: +- Full executionTrace (one step per node, with realistic input/output data) +- Identified predictedFailurePoints (null references, missing fields, credential errors, API limits) +- ChaosTestResults: simulate timeout, null_data, malformed_input, expired_credentials, rate_limit, network_error +- readinessScore (0-100): based on trace success rate, failure severity, chaos resilience +- riskAnalysis: overallRisk, specific risks, mitigations +- passed: true only if readinessScore >= 70 AND no critical failure points +- deploymentBlockers: list of issues that MUST be fixed before deployment`, + }, + ], { + temperature: 0.1, + retries: 3, + }); + + return this.normalizeReport(jobId, result, mockData); + } + + private normalizeReport( + jobId: string, + raw: Partial, + mockData: Record, + ): SimulationReport { + const readinessScore = Math.min(100, Math.max(0, raw.readinessScore ?? 0)); + return { + jobId, + passed: raw.passed ?? readinessScore >= 70, + readinessScore, + executionTrace: Array.isArray(raw.executionTrace) ? raw.executionTrace : [], + riskAnalysis: raw.riskAnalysis ?? { overallRisk: 'medium', risks: [], mitigations: [] }, + predictedFailurePoints: Array.isArray(raw.predictedFailurePoints) ? raw.predictedFailurePoints : [], + chaosTestResults: Array.isArray(raw.chaosTestResults) ? raw.chaosTestResults : [], + deploymentBlockers: Array.isArray(raw.deploymentBlockers) ? raw.deploymentBlockers : [], + mockDataUsed: mockData, + simulatedAt: new Date().toISOString(), + }; + } +} diff --git a/apps/simulator/src/agents/validator.ts b/apps/simulator/src/agents/validator.ts new file mode 100644 index 0000000000000000000000000000000000000000..94422b7fb359af50845e4b341d9a9cece3e3362f --- /dev/null +++ b/apps/simulator/src/agents/validator.ts @@ -0,0 +1,486 @@ +/** + * Validation Engine Agent — UPGRADED + * Multi-stage: Schema → Credential → Graph → Expression → Reliability → NodeRegistry + * Provider-agnostic LLM Gateway — NO direct OpenAI dependency + * Strict: rejects unknown node types, validates real expressions, checks completeness + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { + N8nWorkflow, + WorkflowGraph, + ValidationReport, + ValidationSection, + ValidationIssue, +} from '../types/workflow'; +import { isValidNodeType, getNodeDef } from '../knowledge/nodeRegistry'; + +export class ValidationEngine { + private llm: LLMGateway; + private n8nBaseUrl: string; + private n8nApiKey: string; + + constructor(llm: LLMGateway, n8nBaseUrl: string, n8nApiKey: string) { + this.llm = llm; + this.n8nBaseUrl = n8nBaseUrl; + this.n8nApiKey = n8nApiKey; + } + + async validate(jobId: string, workflow: N8nWorkflow, graph: WorkflowGraph): Promise { + const [ + schemaValidation, + nodeRegistryValidation, + graphValidation, + expressionValidation, + dataFlowValidation, + reliabilityValidation, + credentialValidation, + ] = await Promise.all([ + this.validateSchema(workflow), + this.validateNodeRegistry(workflow), + this.validateGraph(graph), + this.validateExpressions(workflow), + this.validateDataFlow(workflow, graph), + this.validateReliability(workflow, graph), + this.validateCredentials(workflow), + ]); + + const allIssues = [ + ...schemaValidation.issues, + ...nodeRegistryValidation.issues, + ...graphValidation.issues, + ...expressionValidation.issues, + ...dataFlowValidation.issues, + ...reliabilityValidation.issues, + ...credentialValidation.issues, + ]; + + const errors = allIssues.filter((i) => i.severity === 'error'); + const warnings = allIssues.filter((i) => i.severity === 'warning'); + + const overallScore = Math.round( + schemaValidation.score * 0.15 + + nodeRegistryValidation.score * 0.20 + + credentialValidation.score * 0.15 + + graphValidation.score * 0.15 + + expressionValidation.score * 0.15 + + dataFlowValidation.score * 0.10 + + reliabilityValidation.score * 0.10, + ); + + return { + jobId, + valid: errors.length === 0, + overallScore, + schemaValidation, + credentialValidation, + graphValidation, + expressionValidation, + reliabilityValidation, + nodeRegistryValidation, + dataFlowValidation, + issues: errors, + warnings, + validatedAt: new Date().toISOString(), + }; + } + + // ─── A. Schema Validation ───────────────────────────────────────────────── + private async validateSchema(workflow: N8nWorkflow): Promise { + const issues: ValidationIssue[] = []; + + if (!workflow.name || workflow.name.trim() === '') { + issues.push(this.issue('schema-001', 'error', 'Schema', undefined, + 'Workflow must have a name', 'Add a descriptive workflow name')); + } + + if (!workflow.nodes || workflow.nodes.length === 0) { + issues.push(this.issue('schema-002', 'error', 'Schema', undefined, + 'Workflow has no nodes', 'Add at least one trigger node')); + } + + if ((workflow.active as any) === true) { + issues.push(this.issue('schema-active', 'error', 'Schema', undefined, + 'Workflow active must be false — NEVER deploy as active', + 'Set active: false — activation requires human approval')); + } + + workflow.nodes?.forEach((node) => { + if (!node.type) { + issues.push(this.issue('schema-003', 'error', 'Schema', node.id, + `Node "${node.name}" is missing type`, 'Provide a valid n8n node type')); + } + if (!node.typeVersion || node.typeVersion < 1) { + issues.push(this.issue('schema-004', 'warning', 'Schema', node.id, + `Node "${node.name}" missing typeVersion`, 'Add typeVersion: 1 or higher')); + } + if (!node.position || !Array.isArray(node.position) || node.position.length !== 2) { + issues.push(this.issue('schema-005', 'warning', 'Schema', node.id, + `Node "${node.name}" missing position [x, y]`, 'Add [x, y] position array')); + } + if (!node.id || node.id.trim() === '') { + issues.push(this.issue('schema-006', 'error', 'Schema', undefined, + `Node "${node.name}" is missing an ID`, 'Add a unique UUID string as node ID')); + } + }); + + // Check for duplicate node IDs + const nodeIds = workflow.nodes?.map((n) => n.id) ?? []; + const seen = new Set(); + nodeIds.forEach((id) => { + if (seen.has(id)) { + issues.push(this.issue('schema-007', 'error', 'Schema', id, + `Duplicate node ID detected: "${id}"`, 'Ensure all node IDs are unique UUIDs')); + } + seen.add(id); + }); + + const score = Math.max(0, 100 - errors(issues) * 20 - warnings(issues) * 5); + return { passed: errors(issues) === 0, score, issues }; + } + + // ─── B. Node Registry Validation (CRITICAL) ────────────────────────────── + private async validateNodeRegistry(workflow: N8nWorkflow): Promise { + const issues: ValidationIssue[] = []; + + workflow.nodes?.forEach((node) => { + if (!node.type) return; + + if (!isValidNodeType(node.type)) { + issues.push(this.issue(`registry-unknown-${node.id}`, 'error', 'NodeRegistry', node.id, + `UNKNOWN node type "${node.type}" — not in n8n registry. This will fail on import.`, + 'Replace with a valid node type from the registry')); + return; + } + + const def = getNodeDef(node.type); + if (!def) return; + + // Check required parameters are present + def.requiredParameters.forEach((param) => { + const paramsStr = JSON.stringify(node.parameters ?? {}); + if (!paramsStr.includes(`"${param}"`)) { + issues.push(this.issue(`registry-param-${node.id}-${param}`, 'warning', 'NodeRegistry', node.id, + `Node "${node.name}" may be missing required parameter: "${param}"`, + `Add "${param}" parameter to node "${node.name}"`)); + } + }); + + // Check node has parameters (no completely empty nodes) + if (!node.parameters || Object.keys(node.parameters).length === 0) { + if (def.requiredParameters.length > 0) { + issues.push(this.issue(`registry-empty-${node.id}`, 'error', 'NodeRegistry', node.id, + `Node "${node.name}" has empty parameters — this will not execute correctly`, + `Add required parameters: ${def.requiredParameters.join(', ')}`)); + } + } + + // Verify typeVersion matches registry + if (node.typeVersion && node.typeVersion > def.typeVersion) { + issues.push(this.issue(`registry-version-${node.id}`, 'warning', 'NodeRegistry', node.id, + `Node "${node.name}" uses typeVersion ${node.typeVersion} but registry has v${def.typeVersion}`, + `Use typeVersion: ${def.typeVersion} for "${node.type}"`)); + } + }); + + const score = Math.max(0, 100 - errors(issues) * 25 - warnings(issues) * 8); + return { passed: errors(issues) === 0, score, issues }; + } + + // ─── C. Credential Validation ───────────────────────────────────────────── + private async validateCredentials(workflow: N8nWorkflow): Promise { + const issues: ValidationIssue[] = []; + + let availableCredTypes = new Set(); + try { + const resp = await fetch(`${this.n8nBaseUrl}/api/v1/credentials`, { + headers: { 'X-N8N-API-KEY': this.n8nApiKey }, + signal: AbortSignal.timeout(5000), + }); + if (resp.ok) { + const data = await resp.json() as { data: Array<{ type: string }> }; + availableCredTypes = new Set(data.data.map((c) => c.type)); + } + } catch { + issues.push(this.issue('cred-001', 'warning', 'Credentials', undefined, + 'Could not connect to n8n to verify credentials — verify manually', + 'Ensure n8n is accessible at configured URL')); + } + + workflow.nodes?.forEach((node) => { + // Check registry-defined credential + const def = getNodeDef(node.type); + if (def?.credentialType) { + if (availableCredTypes.size > 0 && !availableCredTypes.has(def.credentialType)) { + issues.push(this.issue(`cred-missing-${node.id}`, 'error', 'Credentials', node.id, + `Node "${node.name}" requires credential type "${def.credentialType}" which is not in n8n`, + `Create a "${def.credentialDisplayName ?? def.credentialType}" credential in n8n`)); + } + } + + // Also check node.credentials object + if (node.credentials) { + Object.entries(node.credentials).forEach(([credType]) => { + if (availableCredTypes.size > 0 && !availableCredTypes.has(credType)) { + issues.push(this.issue(`cred-node-${node.id}-${credType}`, 'error', 'Credentials', node.id, + `Node "${node.name}" references credential "${credType}" not found in n8n`, + `Create a "${credType}" credential in n8n`)); + } + }); + } + }); + + const score = Math.max(0, 100 - errors(issues) * 25); + return { passed: errors(issues) === 0, score, issues }; + } + + // ─── D. Graph Validation ────────────────────────────────────────────────── + private async validateGraph(graph: WorkflowGraph): Promise { + const issues: ValidationIssue[] = []; + const nodeIds = new Set(graph.nodes.map((n) => n.id)); + + // Orphan detection (non-trigger nodes with no incoming edges) + graph.nodes.forEach((node) => { + if (node.layer !== 'trigger') { + const hasIncoming = graph.edges.some((e) => e.targetNodeId === node.id); + if (!hasIncoming) { + issues.push(this.issue('graph-orphan-' + node.id, 'error', 'Graph', node.id, + `Node "${node.label}" is orphaned — no incoming edges`, + 'Connect this node to the workflow or remove it')); + } + } + }); + + // Dead-end detection (non-terminal nodes with no outgoing edges) + graph.nodes.forEach((node) => { + const isTerminal = ['monitoring', 'utility'].includes(node.layer ?? ''); + const hasOutgoing = graph.edges.some((e) => e.sourceNodeId === node.id); + if (!isTerminal && !hasOutgoing && node.layer !== 'trigger') { + // Warning only — some nodes legitimately end flows + issues.push(this.issue('graph-deadend-' + node.id, 'warning', 'Graph', node.id, + `Node "${node.label}" has no outgoing edges — is this intentional?`, + 'Connect to a next step or add a terminal node')); + } + }); + + // Edge validity + graph.edges.forEach((edge) => { + if (!nodeIds.has(edge.sourceNodeId)) { + issues.push(this.issue('graph-edge-src-' + edge.id, 'error', 'Graph', undefined, + `Edge "${edge.id}" references missing source node "${edge.sourceNodeId}"`, + 'Fix edge source reference')); + } + if (!nodeIds.has(edge.targetNodeId)) { + issues.push(this.issue('graph-edge-tgt-' + edge.id, 'error', 'Graph', undefined, + `Edge "${edge.id}" references missing target node "${edge.targetNodeId}"`, + 'Fix edge target reference')); + } + }); + + // Trigger check + const hasTrigger = graph.nodes.some((n) => n.layer === 'trigger'); + if (!hasTrigger) { + issues.push(this.issue('graph-no-trigger', 'error', 'Graph', undefined, + 'Workflow has no trigger node', + 'Add a trigger layer node: Webhook, Schedule, Telegram Trigger, etc.')); + } + + const score = Math.max(0, 100 - errors(issues) * 20 - warnings(issues) * 5); + return { passed: errors(issues) === 0, score, issues }; + } + + // ─── E. Expression Validation (UPGRADED) ───────────────────────────────── + private async validateExpressions(workflow: N8nWorkflow): Promise { + const issues: ValidationIssue[] = []; + const expressionRegex = /\{\{(.+?)\}\}/g; + + workflow.nodes?.forEach((node) => { + const paramsStr = JSON.stringify(node.parameters ?? {}); + const matches = [...paramsStr.matchAll(expressionRegex)]; + + // Check for completely static nodes that should have expressions + const def = getNodeDef(node.type); + if (def && matches.length === 0 && def.requiredParameters.length > 0 && node.type !== 'n8n-nodes-base.manualTrigger') { + const isTrigger = def.isTrigger; + if (!isTrigger && !['n8n-nodes-base.noOp', 'n8n-nodes-base.stickyNote'].includes(node.type)) { + issues.push(this.issue(`expr-static-${node.id}`, 'warning', 'Expressions', node.id, + `Node "${node.name}" has no dynamic expressions — likely using static/hardcoded values`, + 'Add dynamic expressions like {{$json?.field ?? ""}} to reference upstream data')); + } + } + + matches.forEach((match) => { + const expr = match[1]?.trim() ?? ''; + + // Detect unsafe dot-access without optional chaining + if ( + expr.includes('$json.') || + expr.includes('$node[') && !expr.includes('?.') && !expr.includes(' ?? ') + ) { + issues.push(this.issue( + `expr-unsafe-${node.id}-${Buffer.from(expr).toString('base64').slice(0, 8)}`, + 'warning', 'Expressions', node.id, + `Expression "{{${expr}}}" may throw if value is null — use optional chaining`, + `Change to: {{${expr.replace(/\./g, '?.')} ?? ''}}`)); + } + + // Detect empty expressions + if (expr === '' || expr === '""' || expr === "''") { + issues.push(this.issue(`expr-empty-${node.id}`, 'error', 'Expressions', node.id, + `Node "${node.name}" has empty expression {{${expr}}}`, + 'Replace with a real expression referencing upstream data')); + } + + // Detect placeholder text patterns + const placeholderPatterns = ['YOUR_', 'REPLACE_ME', 'TODO', 'PLACEHOLDER', 'example.com', 'XXXX']; + placeholderPatterns.forEach((p) => { + if (expr.toUpperCase().includes(p)) { + issues.push(this.issue(`expr-placeholder-${node.id}`, 'error', 'Expressions', node.id, + `Node "${node.name}" contains placeholder text in expression: "${expr}"`, + 'Replace placeholder with actual value or expression')); + } + }); + }); + + // Detect placeholder strings in parameters (outside expressions) + const plainParams = JSON.stringify(node.parameters ?? {}); + const placeholderStrings = ['YOUR_', 'REPLACE_ME', 'example.com', 'your-', 'my-webhook-path']; + placeholderStrings.forEach((p) => { + if (plainParams.includes(p)) { + issues.push(this.issue(`param-placeholder-${node.id}-${p}`, 'warning', 'Expressions', node.id, + `Node "${node.name}" may contain placeholder value: "${p}"`, + 'Replace placeholder with real configuration value')); + } + }); + }); + + const score = Math.max(0, 100 - errors(issues) * 25 - warnings(issues) * 5); + return { passed: errors(issues) === 0, score, issues }; + } + + // ─── F. Data Flow Validation (NEW) ─────────────────────────────────────── + private async validateDataFlow(workflow: N8nWorkflow, graph: WorkflowGraph): Promise { + const issues: ValidationIssue[] = []; + + // Check that connected nodes actually reference upstream data + graph.edges.forEach((edge) => { + const targetNode = workflow.nodes.find((n) => + n.name === graph.nodes.find((gn) => gn.id === edge.targetNodeId)?.label, + ); + + if (targetNode?.parameters) { + const paramsStr = JSON.stringify(targetNode.parameters); + const hasExpression = paramsStr.includes('{{') || paramsStr.includes('$json') || paramsStr.includes('$node'); + + // If node has inputs but no expressions at all, it's likely disconnected data-flow + const def = getNodeDef(targetNode.type); + if ( + def && + !def.isTrigger && + !hasExpression && + !['n8n-nodes-base.noOp', 'n8n-nodes-base.stickyNote', 'n8n-nodes-base.wait'].includes(targetNode.type) + ) { + issues.push(this.issue(`dataflow-${edge.id}`, 'warning', 'DataFlow', targetNode.id, + `Node "${targetNode.name}" receives data but has no expressions referencing upstream fields`, + 'Add expressions like {{$json?.fieldName ?? ""}} to consume upstream data')); + } + } + }); + + // SET node — verify values array is not empty + workflow.nodes?.filter((n) => n.type === 'n8n-nodes-base.set').forEach((node) => { + const values = (node.parameters as Record)?.fields; + if (!values || JSON.stringify(values) === '{"values":[]}' || JSON.stringify(values) === '[]') { + issues.push(this.issue(`dataflow-set-empty-${node.id}`, 'error', 'DataFlow', node.id, + `SET node "${node.name}" has empty fields — it will output nothing`, + 'Add field mappings: name → expression pairs')); + } + }); + + // CODE node — verify jsCode is not a placeholder + workflow.nodes?.filter((n) => n.type === 'n8n-nodes-base.code').forEach((node) => { + const jsCode = (node.parameters as Record)?.jsCode as string | undefined; + if (!jsCode || jsCode.trim() === '' || jsCode.includes('// TODO') || jsCode === 'return [];') { + issues.push(this.issue(`dataflow-code-${node.id}`, 'error', 'DataFlow', node.id, + `Code node "${node.name}" has empty or placeholder code`, + 'Add real JavaScript logic that processes $input.all() items')); + } + }); + + // IF node — verify conditions reference real fields + workflow.nodes?.filter((n) => n.type === 'n8n-nodes-base.if').forEach((node) => { + const conditions = (node.parameters as Record)?.conditions as Record | undefined; + const condList = (conditions?.conditions as unknown[]) ?? []; + if (condList.length === 0) { + issues.push(this.issue(`dataflow-if-${node.id}`, 'error', 'DataFlow', node.id, + `IF node "${node.name}" has no conditions defined`, + 'Add at least one condition referencing a real upstream JSON field')); + } + }); + + const score = Math.max(0, 100 - errors(issues) * 20 - warnings(issues) * 8); + return { passed: errors(issues) === 0, score, issues }; + } + + // ─── G. Reliability Validation ─────────────────────────────────────────── + private async validateReliability(workflow: N8nWorkflow, graph: WorkflowGraph): Promise { + const issues: ValidationIssue[] = []; + + // Retry policy on critical/external nodes + const criticalNodes = graph.nodes.filter((n) => n.isCritical); + criticalNodes.forEach((node) => { + const n8nNode = workflow.nodes.find((n) => n.name === node.label); + if (n8nNode && !n8nNode.retryOnFail) { + issues.push(this.issue(`rel-retry-${node.id}`, 'warning', 'Reliability', node.id, + `Critical node "${node.label}" has no retry policy`, + 'Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000')); + } + }); + + // Check external API nodes have retry + workflow.nodes?.forEach((node) => { + const def = getNodeDef(node.type); + if (def?.supportsRetry && !node.retryOnFail) { + issues.push(this.issue(`rel-external-retry-${node.id}`, 'warning', 'Reliability', node.id, + `External node "${node.name}" (${node.type}) has no retry — may fail on transient errors`, + 'Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000')); + } + }); + + // Error workflow + if (!workflow.settings?.errorWorkflow) { + issues.push(this.issue('rel-error-workflow', 'warning', 'Reliability', undefined, + 'No error workflow configured', + 'Set settings.errorWorkflow to an alerting/notification workflow ID')); + } + + // Monitoring nodes + const hasMonitoring = graph.nodes.some((n) => n.layer === 'monitoring'); + if (!hasMonitoring) { + issues.push(this.issue('rel-monitoring', 'warning', 'Reliability', undefined, + 'No monitoring/observability layer nodes detected', + 'Add logging or notification nodes for observability')); + } + + const score = Math.max(0, 100 - errors(issues) * 20 - warnings(issues) * 8); + return { passed: errors(issues) === 0, score, issues }; + } + + private issue( + id: string, + severity: ValidationIssue['severity'], + category: string, + nodeId: string | undefined, + message: string, + suggestion: string, + ): ValidationIssue { + return { id, severity, category, nodeId, message, suggestion }; + } +} + +// helpers +function errors(issues: ValidationIssue[]): number { + return issues.filter((i) => i.severity === 'error').length; +} +function warnings(issues: ValidationIssue[]): number { + return issues.filter((i) => i.severity === 'warning').length; +} diff --git a/apps/simulator/src/agents/workflowPlanner.ts b/apps/simulator/src/agents/workflowPlanner.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e9c8497058ca58711e630f40e70386bf54d5e46 --- /dev/null +++ b/apps/simulator/src/agents/workflowPlanner.ts @@ -0,0 +1,36 @@ +/** + * Workflow Planner Agent — UPGRADED + * Designs workflows like a senior engineer using layered architecture + * Provider-agnostic LLM Gateway (no direct OpenAI dependency) + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { WorkflowIntent, WorkflowArchitecturePlan } from '../types/workflow'; +import { WORKFLOW_PLANNER_PROMPT } from '../prompts/workflowPlanner'; + +export class WorkflowPlannerAgent { + private llm: LLMGateway; + + constructor(llm: LLMGateway) { + this.llm = llm; + } + + async plan(userRequest: string, intent: WorkflowIntent): Promise { + return this.llm.completeJSON([ + { role: 'system', content: WORKFLOW_PLANNER_PROMPT }, + { + role: 'user', + content: `Design a production-grade workflow architecture for this request: + +REQUEST: ${userRequest} + +INTENT ANALYSIS: +${JSON.stringify(intent, null, 2)} + +Return a complete WorkflowArchitecturePlan JSON following the specification.`, + }, + ], { + temperature: 0.1, + retries: 3, + }); + } +} diff --git a/apps/simulator/src/index.ts b/apps/simulator/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3fa2cfd0832c2e4dca2f3566b35128cc789a06b8 --- /dev/null +++ b/apps/simulator/src/index.ts @@ -0,0 +1,96 @@ +/** + * Simulator Service - Main Entry Point — UPGRADED + * External Node.js compute service (Express.js) + * Handles all CPU-intensive AI orchestration logic offloaded from Cloudflare Worker + * Provider-agnostic LLM Gateway — NO direct OpenAI SDK dependency + */ +import * as moduleAlias from 'module-alias'; +import path from 'path'; + +// Register module aliases for production +moduleAlias.addAliases({ + '@wfo/core': path.join(__dirname, '../../../core'), + '@wfo/integrations': path.join(__dirname, '../../../integrations'), + '@wfo/validation': path.join(__dirname, '../../../validation'), + '@wfo/observability': path.join(__dirname, '../../../observability'), + '@wfo/knowledge': path.join(__dirname, '../../../knowledge'), + '@wfo/config': path.join(__dirname, '../../../config'), + '@wfo/apps/worker': path.join(__dirname, '../../../apps/worker'), +}); + +import express from 'express'; +import helmet from 'helmet'; +import cors from 'cors'; +import { pino } from 'pino'; +import 'dotenv/config'; + +import { internalAuthMiddleware } from './middleware/internalAuth'; +import { generateRoute } from './routes/generate'; +import { validateRoute } from './routes/validate'; +import { simulateRoute } from './routes/simulate'; + +export const logger = pino({ + transport: { + target: 'pino-pretty', + options: { colorize: true }, + }, + level: process.env['LOG_LEVEL'] ?? 'info', +}); + +const app: express.Application = express(); + +// ─── Security Middleware ────────────────────────────────────────────────────── +app.use(helmet()); +app.use(cors({ origin: process.env['WORKER_ORIGIN'] ?? '*' })); +app.use(express.json({ limit: '4mb' })); + +// ─── Internal Auth (shared secret with CF Worker) ──────────────────────────── +app.use('/process', internalAuthMiddleware); + +// ─── Routes ─────────────────────────────────────────────────────────────────── +app.use('/process/generate', generateRoute); +app.use('/process/validate', validateRoute); +app.use('/process/simulate', simulateRoute); + +// ─── Health (public) ────────────────────────────────────────────────────────── +app.get('/health', (_req, res) => { + res.json({ + status: 'ok', + service: 'wfo-simulator', + version: '2.0.0', + uptime: process.uptime(), + capabilities: { + llmGateway: 'provider-agnostic (openai | anthropic | openai-compatible | custom)', + swarmOrchestration: 'parallel multi-agent workflow design (up to 5 candidates)', + selfHealing: 'automatic validation fix + AI-assisted healing', + webhookAutoBind: 'auto trigger detection and configuration', + memorySystem: 'in-process pattern learning and anti-pattern prevention', + nodeRegistry: `strict — ${Object.keys(require('./knowledge/nodeRegistry').N8N_NODE_REGISTRY).length} verified node types`, + }, + config: { + llmProvider: process.env['LLM_PROVIDER'] ?? 'openai', + llmModel: process.env['LLM_MODEL'] ?? 'gpt-4o', + llmGatewayUrl: process.env['LLM_GATEWAY_URL'] ?? 'https://api.openai.com/v1', + hasFallback: !!(process.env['LLM_FALLBACK_URL'] && process.env['LLM_FALLBACK_KEY']), + }, + }); +}); + +// ─── Global Error Handler ───────────────────────────────────────────────────── +app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { + logger.error({ err }, 'Unhandled error'); + res.status(500).json({ success: false, error: err.message }); +}); + +const PORT = Number(process.env["PORT"] ?? 7860); +app.listen(PORT, () => { + logger.info(`[Simulator v2.0] Running on port ${PORT}`); + logger.info(`[Simulator] LLM provider: ${process.env['LLM_PROVIDER'] ?? 'openai'}`); + logger.info(`[Simulator] LLM model: ${process.env['LLM_MODEL'] ?? 'gpt-4o'}`); + logger.info(`[Simulator] Swarm: enabled (up to 5 parallel designs)`); + logger.info(`[Simulator] Self-Healing: enabled`); + logger.info(`[Simulator] Memory: enabled`); + logger.info(`[Simulator] Webhook Auto-Bind: enabled`); +}); + +export default app; diff --git a/apps/simulator/src/knowledge/nodeRegistry.ts b/apps/simulator/src/knowledge/nodeRegistry.ts new file mode 100644 index 0000000000000000000000000000000000000000..0cc4dbb734db3c56601914dc6dfd523fe863d9b6 --- /dev/null +++ b/apps/simulator/src/knowledge/nodeRegistry.ts @@ -0,0 +1,1078 @@ +/** + * n8n Node Registry — PRODUCTION GRADE + * Real node definitions: type, version, parameters schema, credentials, I/O structure + * ZERO hallucinated nodes. ZERO placeholder types. ZERO unknown nodes. + * Source: n8n official node definitions (github.com/n8n-io/n8n) + */ + +export interface NodeRegistryEntry { + type: string; + displayName: string; + typeVersion: number; + group: string[]; + description: string; + isTrigger: boolean; + credentialType?: string; + credentialDisplayName?: string; + inputs: string[]; + outputs: string[]; + outputNames?: string[]; + requiredParameters: string[]; + optionalParameters: string[]; + defaultParameters: Record; + executionBehavior: 'once' | 'perItem' | 'trigger' | 'webhook'; + supportsRetry: boolean; + layer: 'trigger' | 'transform' | 'ai' | 'integration' | 'control' | 'database' | 'utility'; + tags: string[]; +} + +export const N8N_NODE_REGISTRY: Record = { + + // ─── TRIGGER NODES ────────────────────────────────────────────────────────── + + 'n8n-nodes-base.webhook': { + type: 'n8n-nodes-base.webhook', + displayName: 'Webhook', + typeVersion: 2, + group: ['trigger'], + description: 'Starts the workflow when a webhook is received', + isTrigger: true, + inputs: [], + outputs: ['main'], + requiredParameters: ['httpMethod', 'path'], + optionalParameters: ['responseMode', 'responseData', 'responseCode', 'responseHeaders', 'rawBody', 'options'], + defaultParameters: { + httpMethod: 'POST', + path: '={{$runIndex === 0 ? "auto-webhook-" + $workflow.id : ""}}', + responseMode: 'onReceived', + responseData: 'allEntries', + }, + executionBehavior: 'webhook', + supportsRetry: false, + layer: 'trigger', + tags: ['trigger', 'http', 'webhook'], + }, + + 'n8n-nodes-base.scheduleTrigger': { + type: 'n8n-nodes-base.scheduleTrigger', + displayName: 'Schedule Trigger', + typeVersion: 1, + group: ['trigger'], + description: 'Triggers workflow on a schedule (cron or interval)', + isTrigger: true, + inputs: [], + outputs: ['main'], + requiredParameters: ['rule'], + optionalParameters: [], + defaultParameters: { + rule: { + interval: [{ field: 'hours', hoursInterval: 1 }], + }, + }, + executionBehavior: 'trigger', + supportsRetry: false, + layer: 'trigger', + tags: ['trigger', 'cron', 'schedule'], + }, + + 'n8n-nodes-base.manualTrigger': { + type: 'n8n-nodes-base.manualTrigger', + displayName: 'Manual Trigger', + typeVersion: 1, + group: ['trigger'], + description: 'Starts workflow execution manually via the UI', + isTrigger: true, + inputs: [], + outputs: ['main'], + requiredParameters: [], + optionalParameters: [], + defaultParameters: {}, + executionBehavior: 'trigger', + supportsRetry: false, + layer: 'trigger', + tags: ['trigger', 'manual'], + }, + + 'n8n-nodes-base.telegramTrigger': { + type: 'n8n-nodes-base.telegramTrigger', + displayName: 'Telegram Trigger', + typeVersion: 1, + group: ['trigger'], + description: 'Starts workflow when a Telegram message or event is received', + isTrigger: true, + credentialType: 'telegramApi', + credentialDisplayName: 'Telegram API', + inputs: [], + outputs: ['main'], + requiredParameters: ['updates'], + optionalParameters: ['additionalFields'], + defaultParameters: { + updates: ['message'], + }, + executionBehavior: 'webhook', + supportsRetry: false, + layer: 'trigger', + tags: ['trigger', 'telegram', 'messaging'], + }, + + 'n8n-nodes-base.emailReadImap': { + type: 'n8n-nodes-base.emailReadImap', + displayName: 'Email Trigger (IMAP)', + typeVersion: 2, + group: ['trigger'], + description: 'Triggers workflow when a new email is received via IMAP', + isTrigger: true, + credentialType: 'imap', + credentialDisplayName: 'IMAP', + inputs: [], + outputs: ['main'], + requiredParameters: ['mailbox', 'action'], + optionalParameters: ['downloadAttachments', 'format', 'options'], + defaultParameters: { + mailbox: 'INBOX', + action: 'read', + }, + executionBehavior: 'trigger', + supportsRetry: false, + layer: 'trigger', + tags: ['trigger', 'email', 'imap'], + }, + + 'n8n-nodes-base.githubTrigger': { + type: 'n8n-nodes-base.githubTrigger', + displayName: 'GitHub Trigger', + typeVersion: 1, + group: ['trigger'], + description: 'Triggers on GitHub events (push, PR, issue, etc.)', + isTrigger: true, + credentialType: 'githubApi', + credentialDisplayName: 'GitHub API', + inputs: [], + outputs: ['main'], + requiredParameters: ['owner', 'repository', 'events'], + optionalParameters: [], + defaultParameters: { + events: ['push'], + }, + executionBehavior: 'webhook', + supportsRetry: false, + layer: 'trigger', + tags: ['trigger', 'github', 'devops'], + }, + + 'n8n-nodes-base.slackTrigger': { + type: 'n8n-nodes-base.slackTrigger', + displayName: 'Slack Trigger', + typeVersion: 1, + group: ['trigger'], + description: 'Triggers on Slack events (messages, reactions, etc.)', + isTrigger: true, + credentialType: 'slackOAuth2Api', + credentialDisplayName: 'Slack OAuth2 API', + inputs: [], + outputs: ['main'], + requiredParameters: ['trigger'], + optionalParameters: ['channelId', 'watchedEvent'], + defaultParameters: { + trigger: 'any_message', + }, + executionBehavior: 'webhook', + supportsRetry: false, + layer: 'trigger', + tags: ['trigger', 'slack', 'messaging'], + }, + + 'n8n-nodes-base.notionTrigger': { + type: 'n8n-nodes-base.notionTrigger', + displayName: 'Notion Trigger', + typeVersion: 1, + group: ['trigger'], + description: 'Triggers when a Notion database page is created or updated', + isTrigger: true, + credentialType: 'notionApi', + credentialDisplayName: 'Notion API', + inputs: [], + outputs: ['main'], + requiredParameters: ['databaseId', 'event'], + optionalParameters: ['simple'], + defaultParameters: { + event: 'page_added', + }, + executionBehavior: 'trigger', + supportsRetry: false, + layer: 'trigger', + tags: ['trigger', 'notion', 'productivity'], + }, + + // ─── AI / LLM NODES ───────────────────────────────────────────────────────── + + '@n8n/n8n-nodes-langchain.agent': { + type: '@n8n/n8n-nodes-langchain.agent', + displayName: 'AI Agent', + typeVersion: 1, + group: ['transform'], + description: 'AI Agent powered by LangChain with tools and memory', + isTrigger: false, + inputs: ['main', 'ai_tool', 'ai_memory', 'ai_languageModel'], + outputs: ['main'], + requiredParameters: ['text', 'options'], + optionalParameters: ['systemMessage', 'promptType'], + defaultParameters: { + text: '={{ $json?.message ?? $json?.text ?? $json?.body?.message ?? "" }}', + options: { + systemMessage: 'You are a helpful AI assistant. Be concise and accurate.', + }, + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'ai', + tags: ['ai', 'agent', 'langchain', 'llm'], + }, + + '@n8n/n8n-nodes-langchain.openAi': { + type: '@n8n/n8n-nodes-langchain.openAi', + displayName: 'OpenAI Chat Model', + typeVersion: 1, + group: ['transform'], + description: 'OpenAI Chat Model for use with AI Agent or Chain nodes', + isTrigger: false, + credentialType: 'openAiApi', + credentialDisplayName: 'OpenAI API', + inputs: ['ai_languageModel'], + outputs: ['ai_languageModel'], + requiredParameters: ['model'], + optionalParameters: ['options'], + defaultParameters: { + model: { __rl: true, value: 'gpt-4o', mode: 'list', cachedResultName: 'GPT-4o' }, + options: { + maxTokens: 2048, + temperature: 0.7, + }, + }, + executionBehavior: 'once', + supportsRetry: true, + layer: 'ai', + tags: ['ai', 'openai', 'llm', 'langchain'], + }, + + '@n8n/n8n-nodes-langchain.lmChatAnthropic': { + type: '@n8n/n8n-nodes-langchain.lmChatAnthropic', + displayName: 'Anthropic Chat Model', + typeVersion: 1, + group: ['transform'], + description: 'Anthropic Claude model for use with AI Agent or Chain nodes', + isTrigger: false, + credentialType: 'anthropicApi', + credentialDisplayName: 'Anthropic API', + inputs: ['ai_languageModel'], + outputs: ['ai_languageModel'], + requiredParameters: ['model'], + optionalParameters: ['options'], + defaultParameters: { + model: 'claude-3-5-sonnet-20241022', + options: { + maxTokens: 2048, + temperature: 0.7, + }, + }, + executionBehavior: 'once', + supportsRetry: true, + layer: 'ai', + tags: ['ai', 'anthropic', 'claude', 'llm', 'langchain'], + }, + + '@n8n/n8n-nodes-langchain.memoryBufferWindow': { + type: '@n8n/n8n-nodes-langchain.memoryBufferWindow', + displayName: 'Window Buffer Memory', + typeVersion: 1, + group: ['transform'], + description: 'Keeps last N messages in memory for AI Agent context', + isTrigger: false, + inputs: ['ai_memory'], + outputs: ['ai_memory'], + requiredParameters: [], + optionalParameters: ['sessionKey', 'sessionIdType', 'contextWindowLength'], + defaultParameters: { + contextWindowLength: 10, + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'ai', + tags: ['ai', 'memory', 'langchain'], + }, + + '@n8n/n8n-nodes-langchain.toolCode': { + type: '@n8n/n8n-nodes-langchain.toolCode', + displayName: 'Code Tool', + typeVersion: 1, + group: ['transform'], + description: 'Custom JavaScript tool for AI Agent', + isTrigger: false, + inputs: ['ai_tool'], + outputs: ['ai_tool'], + requiredParameters: ['name', 'description', 'jsCode'], + optionalParameters: [], + defaultParameters: { + name: 'custom_tool', + description: 'Describe what this tool does so the AI agent knows when to use it', + jsCode: '// Tool logic here\nreturn { result: "success" };', + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'ai', + tags: ['ai', 'tool', 'langchain', 'code'], + }, + + 'n8n-nodes-base.openAi': { + type: 'n8n-nodes-base.openAi', + displayName: 'OpenAI', + typeVersion: 1, + group: ['transform'], + description: 'Use the OpenAI API (Chat, Image, Audio, etc.)', + isTrigger: false, + credentialType: 'openAiApi', + credentialDisplayName: 'OpenAI API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['resource', 'operation'], + optionalParameters: ['model', 'messages', 'options'], + defaultParameters: { + resource: 'chat', + operation: 'complete', + model: { __rl: true, value: 'gpt-4o', mode: 'list', cachedResultName: 'GPT-4o' }, + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'ai', + tags: ['ai', 'openai', 'llm'], + }, + + // ─── TRANSFORM / LOGIC NODES ───────────────────────────────────────────────── + + 'n8n-nodes-base.code': { + type: 'n8n-nodes-base.code', + displayName: 'Code', + typeVersion: 2, + group: ['transform'], + description: 'Run custom JavaScript or Python code on items', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['jsCode'], + optionalParameters: ['mode', 'language', 'pythonCode'], + defaultParameters: { + mode: 'runOnceForAllItems', + language: 'javaScript', + jsCode: `// Available: $input, $json, $items(), $env +const items = $input.all(); +return items.map(item => { + const data = item.json; + return { + json: { + ...data, + processed: true, + processedAt: new Date().toISOString(), + } + }; +});`, + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'transform', + tags: ['code', 'javascript', 'transform'], + }, + + 'n8n-nodes-base.set': { + type: 'n8n-nodes-base.set', + displayName: 'Edit Fields (Set)', + typeVersion: 3, + group: ['transform'], + description: 'Set, add, or remove fields on items', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['fields'], + optionalParameters: ['options', 'include'], + defaultParameters: { + mode: 'manual', + fields: { + values: [ + { + name: 'outputField', + value: '={{ $json?.inputField ?? "" }}', + }, + ], + }, + }, + executionBehavior: 'perItem', + supportsRetry: false, + layer: 'transform', + tags: ['transform', 'set', 'fields'], + }, + + 'n8n-nodes-base.if': { + type: 'n8n-nodes-base.if', + displayName: 'IF', + typeVersion: 2, + group: ['transform'], + description: 'Split items into two branches based on conditions', + isTrigger: false, + inputs: ['main'], + outputs: ['main', 'main'], + outputNames: ['true', 'false'], + requiredParameters: ['conditions'], + optionalParameters: ['combinator'], + defaultParameters: { + conditions: { + options: { + caseSensitive: true, + leftValue: '', + typeValidation: 'strict', + }, + conditions: [ + { + id: 'condition-1', + leftValue: '={{ $json?.status ?? "" }}', + rightValue: 'success', + operator: { + type: 'string', + operation: 'equals', + }, + }, + ], + combinator: 'and', + }, + }, + executionBehavior: 'perItem', + supportsRetry: false, + layer: 'control', + tags: ['control', 'condition', 'branch'], + }, + + 'n8n-nodes-base.switch': { + type: 'n8n-nodes-base.switch', + displayName: 'Switch', + typeVersion: 3, + group: ['transform'], + description: 'Route items to different outputs based on rules', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['rules'], + optionalParameters: ['mode', 'fallbackOutput'], + defaultParameters: { + mode: 'rules', + rules: { + values: [ + { + conditions: { + conditions: [ + { + leftValue: '={{ $json?.type ?? "" }}', + rightValue: 'typeA', + operator: { type: 'string', operation: 'equals' }, + }, + ], + combinator: 'and', + }, + renameOutput: false, + }, + ], + }, + fallbackOutput: 'none', + }, + executionBehavior: 'perItem', + supportsRetry: false, + layer: 'control', + tags: ['control', 'switch', 'routing'], + }, + + 'n8n-nodes-base.merge': { + type: 'n8n-nodes-base.merge', + displayName: 'Merge', + typeVersion: 3, + group: ['transform'], + description: 'Merge data from multiple branches', + isTrigger: false, + inputs: ['main', 'main'], + outputs: ['main'], + requiredParameters: ['mode'], + optionalParameters: ['joinMode', 'clashHandling', 'options'], + defaultParameters: { + mode: 'append', + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'control', + tags: ['control', 'merge', 'combine'], + }, + + 'n8n-nodes-base.splitInBatches': { + type: 'n8n-nodes-base.splitInBatches', + displayName: 'Split In Batches', + typeVersion: 3, + group: ['transform'], + description: 'Split large arrays into smaller batches for processing', + isTrigger: false, + inputs: ['main'], + outputs: ['main', 'main'], + outputNames: ['loop', 'done'], + requiredParameters: ['batchSize'], + optionalParameters: ['options'], + defaultParameters: { + batchSize: 10, + options: {}, + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'control', + tags: ['control', 'batch', 'loop', 'array'], + }, + + 'n8n-nodes-base.itemLists': { + type: 'n8n-nodes-base.itemLists', + displayName: 'Item Lists', + typeVersion: 3, + group: ['transform'], + description: 'Manipulate arrays: split, aggregate, deduplicate, sort', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['operation'], + optionalParameters: ['fieldToSplitOut', 'fieldToAggregate', 'sortFieldsUi', 'options'], + defaultParameters: { + operation: 'splitOutItems', + fieldToSplitOut: 'items', + include: 'noOtherFields', + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'transform', + tags: ['transform', 'array', 'list'], + }, + + 'n8n-nodes-base.filter': { + type: 'n8n-nodes-base.filter', + displayName: 'Filter', + typeVersion: 1, + group: ['transform'], + description: 'Filter items based on conditions, keeping only matching items', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['conditions'], + optionalParameters: [], + defaultParameters: { + conditions: { + options: { caseSensitive: true, typeValidation: 'strict' }, + conditions: [ + { + id: 'filter-1', + leftValue: '={{ $json?.active ?? false }}', + rightValue: true, + operator: { type: 'boolean', operation: 'equals' }, + }, + ], + combinator: 'and', + }, + }, + executionBehavior: 'perItem', + supportsRetry: false, + layer: 'transform', + tags: ['transform', 'filter', 'condition'], + }, + + // ─── HTTP / EXTERNAL API ───────────────────────────────────────────────────── + + 'n8n-nodes-base.httpRequest': { + type: 'n8n-nodes-base.httpRequest', + displayName: 'HTTP Request', + typeVersion: 4, + group: ['output'], + description: 'Make HTTP requests to any API endpoint', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['method', 'url'], + optionalParameters: ['authentication', 'sendHeaders', 'sendQuery', 'sendBody', 'options'], + defaultParameters: { + method: 'GET', + url: '', + options: { + timeout: 10000, + redirect: { + redirect: { followRedirects: true, maxRedirects: 3 }, + }, + }, + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['http', 'api', 'rest', 'integration'], + }, + + // ─── MESSAGING ─────────────────────────────────────────────────────────────── + + 'n8n-nodes-base.telegram': { + type: 'n8n-nodes-base.telegram', + displayName: 'Telegram', + typeVersion: 1, + group: ['output'], + description: 'Send messages, photos, documents via Telegram Bot API', + isTrigger: false, + credentialType: 'telegramApi', + credentialDisplayName: 'Telegram API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['operation', 'chatId'], + optionalParameters: ['text', 'photo', 'document', 'additionalFields'], + defaultParameters: { + operation: 'sendMessage', + chatId: '={{ $json?.message?.chat?.id ?? $json?.chatId ?? "" }}', + text: '={{ $json?.responseText ?? $json?.output ?? "" }}', + additionalFields: { + parse_mode: 'HTML', + }, + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['telegram', 'messaging', 'bot'], + }, + + 'n8n-nodes-base.slack': { + type: 'n8n-nodes-base.slack', + displayName: 'Slack', + typeVersion: 2, + group: ['output'], + description: 'Send messages and interact with Slack', + isTrigger: false, + credentialType: 'slackApi', + credentialDisplayName: 'Slack API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['resource', 'operation', 'channel'], + optionalParameters: ['text', 'blocksUi', 'attachments', 'otherOptions'], + defaultParameters: { + resource: 'message', + operation: 'post', + channel: '', + text: '={{ $json?.message ?? $json?.text ?? "" }}', + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['slack', 'messaging', 'notification'], + }, + + 'n8n-nodes-base.gmail': { + type: 'n8n-nodes-base.gmail', + displayName: 'Gmail', + typeVersion: 2, + group: ['output'], + description: 'Send and manage emails via Gmail', + isTrigger: false, + credentialType: 'googleOAuth2Api', + credentialDisplayName: 'Google OAuth2 API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['operation', 'sendTo', 'subject'], + optionalParameters: ['message', 'attachmentsUi', 'options'], + defaultParameters: { + operation: 'send', + sendTo: '', + subject: '', + message: '', + options: {}, + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['email', 'gmail', 'google'], + }, + + // ─── PRODUCTIVITY / STORAGE ────────────────────────────────────────────────── + + 'n8n-nodes-base.googleSheets': { + type: 'n8n-nodes-base.googleSheets', + displayName: 'Google Sheets', + typeVersion: 4, + group: ['output'], + description: 'Read, write, and manage Google Sheets data', + isTrigger: false, + credentialType: 'googleSheetsOAuth2Api', + credentialDisplayName: 'Google Sheets OAuth2 API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['operation', 'documentId', 'sheetName'], + optionalParameters: ['columns', 'filtersUI', 'options'], + defaultParameters: { + operation: 'appendOrUpdate', + documentId: { __rl: true, value: '', mode: 'id' }, + sheetName: { __rl: true, value: 'Sheet1', mode: 'name' }, + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['google', 'sheets', 'spreadsheet', 'storage'], + }, + + 'n8n-nodes-base.airtable': { + type: 'n8n-nodes-base.airtable', + displayName: 'Airtable', + typeVersion: 2, + group: ['output'], + description: 'Read, write, update, and delete Airtable records', + isTrigger: false, + credentialType: 'airtableTokenApi', + credentialDisplayName: 'Airtable Token API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['operation', 'base', 'table'], + optionalParameters: ['fields', 'filterByFormula', 'sort', 'options'], + defaultParameters: { + operation: 'create', + base: { __rl: true, value: '', mode: 'id' }, + table: { __rl: true, value: '', mode: 'id' }, + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['airtable', 'database', 'storage'], + }, + + 'n8n-nodes-base.notion': { + type: 'n8n-nodes-base.notion', + displayName: 'Notion', + typeVersion: 2, + group: ['output'], + description: 'Create, read, and update Notion pages and databases', + isTrigger: false, + credentialType: 'notionApi', + credentialDisplayName: 'Notion API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['resource', 'operation'], + optionalParameters: ['pageId', 'databaseId', 'title', 'properties', 'blockUi'], + defaultParameters: { + resource: 'page', + operation: 'create', + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['notion', 'productivity', 'database'], + }, + + // ─── DATABASE ──────────────────────────────────────────────────────────────── + + 'n8n-nodes-base.postgres': { + type: 'n8n-nodes-base.postgres', + displayName: 'Postgres', + typeVersion: 2, + group: ['output'], + description: 'Execute SQL queries and interact with PostgreSQL databases', + isTrigger: false, + credentialType: 'postgres', + credentialDisplayName: 'Postgres', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['operation'], + optionalParameters: ['table', 'query', 'columns', 'additionalFields', 'options'], + defaultParameters: { + operation: 'insert', + table: { __rl: true, value: '', mode: 'name' }, + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'database', + tags: ['database', 'sql', 'postgres'], + }, + + 'n8n-nodes-base.mysql': { + type: 'n8n-nodes-base.mysql', + displayName: 'MySQL', + typeVersion: 2, + group: ['output'], + description: 'Execute queries and interact with MySQL databases', + isTrigger: false, + credentialType: 'mySql', + credentialDisplayName: 'MySQL', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['operation'], + optionalParameters: ['table', 'query', 'columns', 'options'], + defaultParameters: { + operation: 'insert', + table: '', + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'database', + tags: ['database', 'sql', 'mysql'], + }, + + 'n8n-nodes-base.redis': { + type: 'n8n-nodes-base.redis', + displayName: 'Redis', + typeVersion: 1, + group: ['output'], + description: 'Get, set, and manage Redis key-value data', + isTrigger: false, + credentialType: 'redis', + credentialDisplayName: 'Redis', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['operation'], + optionalParameters: ['key', 'value', 'expire', 'keyType'], + defaultParameters: { + operation: 'get', + key: '', + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'database', + tags: ['database', 'cache', 'redis'], + }, + + // ─── CRM / BUSINESS ────────────────────────────────────────────────────────── + + 'n8n-nodes-base.hubspot': { + type: 'n8n-nodes-base.hubspot', + displayName: 'HubSpot', + typeVersion: 2, + group: ['output'], + description: 'Manage contacts, deals, companies, and tickets in HubSpot CRM', + isTrigger: false, + credentialType: 'hubspotApi', + credentialDisplayName: 'HubSpot API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['resource', 'operation'], + optionalParameters: ['contactId', 'dealId', 'additionalFields'], + defaultParameters: { + resource: 'contact', + operation: 'create', + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['crm', 'hubspot', 'sales'], + }, + + 'n8n-nodes-base.stripe': { + type: 'n8n-nodes-base.stripe', + displayName: 'Stripe', + typeVersion: 1, + group: ['output'], + description: 'Manage customers, charges, subscriptions via Stripe', + isTrigger: false, + credentialType: 'stripeApi', + credentialDisplayName: 'Stripe API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['resource', 'operation'], + optionalParameters: ['customerId', 'amount', 'currency', 'additionalFields'], + defaultParameters: { + resource: 'customer', + operation: 'get', + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['payment', 'stripe', 'ecommerce'], + }, + + 'n8n-nodes-base.github': { + type: 'n8n-nodes-base.github', + displayName: 'GitHub', + typeVersion: 1, + group: ['output'], + description: 'Interact with GitHub repositories, issues, PRs', + isTrigger: false, + credentialType: 'githubApi', + credentialDisplayName: 'GitHub API', + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['resource', 'operation', 'owner', 'repository'], + optionalParameters: ['title', 'body', 'labels', 'additionalParameters'], + defaultParameters: { + resource: 'issue', + operation: 'create', + owner: '', + repository: '', + }, + executionBehavior: 'perItem', + supportsRetry: true, + layer: 'integration', + tags: ['github', 'devops', 'git'], + }, + + // ─── UTILITY NODES ─────────────────────────────────────────────────────────── + + 'n8n-nodes-base.noOp': { + type: 'n8n-nodes-base.noOp', + displayName: 'No Operation, do nothing', + typeVersion: 1, + group: ['utility'], + description: 'Passes items through without modification (useful as placeholders)', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: [], + optionalParameters: [], + defaultParameters: {}, + executionBehavior: 'perItem', + supportsRetry: false, + layer: 'utility', + tags: ['utility', 'passthrough'], + }, + + 'n8n-nodes-base.stopAndError': { + type: 'n8n-nodes-base.stopAndError', + displayName: 'Stop and Error', + typeVersion: 1, + group: ['utility'], + description: 'Stop workflow execution and throw a custom error', + isTrigger: false, + inputs: ['main'], + outputs: [], + requiredParameters: ['errorType'], + optionalParameters: ['errorMessage', 'errorObject'], + defaultParameters: { + errorType: 'errorMessage', + errorMessage: '={{ "Workflow stopped: " + ($json?.reason ?? "unknown error") }}', + }, + executionBehavior: 'perItem', + supportsRetry: false, + layer: 'utility', + tags: ['utility', 'error', 'stop'], + }, + + 'n8n-nodes-base.wait': { + type: 'n8n-nodes-base.wait', + displayName: 'Wait', + typeVersion: 1, + group: ['utility'], + description: 'Pause workflow execution for a specified time', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['amount', 'unit'], + optionalParameters: [], + defaultParameters: { + amount: 1, + unit: 'seconds', + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'utility', + tags: ['utility', 'wait', 'delay'], + }, + + 'n8n-nodes-base.respondToWebhook': { + type: 'n8n-nodes-base.respondToWebhook', + displayName: 'Respond to Webhook', + typeVersion: 1, + group: ['utility'], + description: 'Send a custom response back to the webhook caller', + isTrigger: false, + inputs: ['main'], + outputs: ['main'], + requiredParameters: ['respondWith'], + optionalParameters: ['responseBody', 'responseCode', 'responseHeaders', 'options'], + defaultParameters: { + respondWith: 'json', + responseBody: '={{ JSON.stringify({ success: true, data: $json }) }}', + responseCode: 200, + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'utility', + tags: ['webhook', 'response', 'utility'], + }, + + 'n8n-nodes-base.stickyNote': { + type: 'n8n-nodes-base.stickyNote', + displayName: 'Sticky Note', + typeVersion: 1, + group: ['annotation'], + description: 'Add documentation notes to the workflow canvas', + isTrigger: false, + inputs: [], + outputs: [], + requiredParameters: ['content'], + optionalParameters: ['height', 'width', 'color'], + defaultParameters: { + content: '## Workflow Documentation\n\nDescribe this section here.', + height: 200, + width: 300, + color: 4, + }, + executionBehavior: 'once', + supportsRetry: false, + layer: 'utility', + tags: ['documentation', 'annotation'], + }, +}; + +// ─── Registry Lookup Helpers ────────────────────────────────────────────────── + +/** + * Get a node definition. Returns undefined if node type is not in registry. + * NEVER return a fallback for unknown types — callers must handle undefined. + */ +export function getNodeDef(nodeType: string): NodeRegistryEntry | undefined { + return N8N_NODE_REGISTRY[nodeType]; +} + +/** + * Validate that a node type exists in the registry + */ +export function isValidNodeType(nodeType: string): boolean { + return nodeType in N8N_NODE_REGISTRY; +} + +/** + * Get all trigger node types + */ +export function getTriggerNodeTypes(): string[] { + return Object.entries(N8N_NODE_REGISTRY) + .filter(([, def]) => def.isTrigger) + .map(([type]) => type); +} + +/** + * Get all node types for a given tag + */ +export function getNodeTypesByTag(tag: string): string[] { + return Object.entries(N8N_NODE_REGISTRY) + .filter(([, def]) => def.tags.includes(tag)) + .map(([type]) => type); +} + +/** + * Get credential type required for a node type + */ +export function getRequiredCredential(nodeType: string): string | undefined { + return N8N_NODE_REGISTRY[nodeType]?.credentialType; +} + +/** + * Get all registered node types as a list (for LLM context injection) + */ +export function getRegistryNodeList(): string { + return Object.entries(N8N_NODE_REGISTRY) + .map(([type, def]) => `- ${type} (v${def.typeVersion}): ${def.description}`) + .join('\n'); +} + +/** + * Get all trigger node types as formatted string for prompts + */ +export function getTriggerNodeList(): string { + return Object.entries(N8N_NODE_REGISTRY) + .filter(([, def]) => def.isTrigger) + .map(([type, def]) => `- ${type}: ${def.description}`) + .join('\n'); +} diff --git a/apps/simulator/src/middleware/internalAuth.ts b/apps/simulator/src/middleware/internalAuth.ts new file mode 100644 index 0000000000000000000000000000000000000000..795d5f806f4ea4bae28e1a28d1885968f882ab7c --- /dev/null +++ b/apps/simulator/src/middleware/internalAuth.ts @@ -0,0 +1,27 @@ +/** + * Internal Auth Middleware + * Validates shared secret between CF Worker and Simulator service + */ +import type { Request, Response, NextFunction } from 'express'; + +export function internalAuthMiddleware(req: Request, res: Response, next: NextFunction): void { + const secret = req.headers['x-internal-secret']; + const expected = process.env['INTERNAL_API_SECRET']; + + if (!expected) { + // No secret configured — skip auth in development + if (process.env['NODE_ENV'] !== 'production') { + next(); + return; + } + res.status(500).json({ success: false, error: 'INTERNAL_API_SECRET not configured' }); + return; + } + + if (!secret || secret !== expected) { + res.status(401).json({ success: false, error: 'Unauthorized — invalid internal secret' }); + return; + } + + next(); +} diff --git a/apps/simulator/src/prompts/compiler.ts b/apps/simulator/src/prompts/compiler.ts new file mode 100644 index 0000000000000000000000000000000000000000..78ab3b9ba0380e110cf6188952c8457e81478645 --- /dev/null +++ b/apps/simulator/src/prompts/compiler.ts @@ -0,0 +1,128 @@ +/** + * Compiler System Prompt — UPGRADED + * Role: Convert WorkflowGraph IR to valid, importable n8n JSON + * Strict expression rules, real data flow, no static placeholders + */ +export const COMPILER_PROMPT = `You are a senior n8n workflow compiler. Convert a WorkflowGraph IR into a valid, importable n8n JSON workflow. + +COMPILATION RULES (NON-NEGOTIABLE): + +1. active MUST always be false — NEVER set active: true under any circumstances +2. All node IDs must be unique UUID strings (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) +3. typeVersion must match actual n8n node version from the schema provided +4. connections MUST properly map all graph edges: + { "NodeDisplayName": { "main": [[{ "node": "TargetDisplayName", "type": "main", "index": 0 }]] } } +5. Node positions MUST be [x, y] tuple arrays (not objects) +6. Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 to ALL external API nodes +7. Add meaningful notes to EVERY node (what it does, what data flows through) +8. NEVER invent parameters not in the schema — only use real n8n node parameters +9. settings MUST include: { "executionOrder": "v1", "saveManualExecutions": true, "timezone": "UTC" } +10. onError: "continueErrorOutput" on all error-boundary nodes + +EXPRESSION RULES (MANDATORY — NO EXCEPTIONS): +All dynamic fields MUST use real n8n expressions: + +✅ CORRECT expressions: +- {{$json?.message ?? ""}} — safe access with nullish coalescing +- {{$json?.body?.text ?? $json?.text ?? ""}} — multi-path with fallback +- {{$node["NodeName"].json?.field ?? ""}} — cross-node reference +- {{$json?.items?.length > 0 ? "yes" : "no"}} — conditional +- {{$json?.chatId ?? $json?.message?.chat?.id ?? ""}} — multiple fallback paths +- {{new Date().toISOString()}} — dynamic date +- {{$json?.price * 1.1}} — arithmetic + +❌ WRONG (never use these): +- "" — empty string +- "YOUR_VALUE_HERE" — placeholder +- "example.com" — example URLs +- "REPLACE_ME" — placeholder +- {{$json.field}} — without optional chaining (may throw null error) + +NODE-SPECIFIC RULES: + +SET NODE — MUST have explicit field mappings: +{ + "mode": "manual", + "fields": { + "values": [ + { "name": "userMessage", "value": "={{$json?.body?.message ?? $json?.message ?? ''}}" }, + { "name": "chatId", "value": "={{$json?.body?.chat?.id ?? $json?.chatId ?? ''}}" } + ] + } +} + +IF NODE — MUST have real conditions referencing actual fields: +{ + "conditions": { + "conditions": [ + { + "id": "cond-1", + "leftValue": "={{$json?.status ?? ''}}", + "rightValue": "active", + "operator": { "type": "string", "operation": "equals" } + } + ], + "combinator": "and" + } +} + +CODE NODE — MUST have real JavaScript logic: +jsCode: "const items = $input.all();\nreturn items.map(item => ({\n json: {\n ...item.json,\n processed: true,\n processedAt: new Date().toISOString()\n }\n}));" + +AI AGENT NODE — MUST have system message + dynamic user input: +{ + "text": "={{$json?.userMessage ?? $json?.message ?? ''}}", + "options": { + "systemMessage": "You are a helpful assistant. Context: {{$json?.context ?? 'None'}}. Be concise and accurate." + } +} + +SWITCH NODE — MUST have real rules: +{ + "mode": "rules", + "rules": { + "values": [{ + "conditions": { + "conditions": [{ "leftValue": "={{$json?.type ?? ''}}", "rightValue": "A", "operator": { "type": "string", "operation": "equals" } }], + "combinator": "and" + }, + "renameOutput": false + }] + } +} + +n8n WORKFLOW JSON SCHEMA: +{ + "name": "Workflow Name", + "active": false, + "nodes": [ + { + "id": "uuid-string", + "name": "Display Name", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [0, 300], + "parameters": { ... real params with expressions ... }, + "credentials": {}, + "onError": "continueErrorOutput", + "retryOnFail": false, + "maxTries": 3, + "waitBetweenTries": 1000, + "notes": "What this node does and why it is here" + } + ], + "connections": { + "Display Name": { + "main": [[{ "node": "Next Node Display Name", "type": "main", "index": 0 }]] + } + }, + "settings": { + "executionOrder": "v1", + "saveManualExecutions": true, + "timezone": "UTC" + }, + "tags": [], + "meta": {} +} + +RETURN ONLY valid JSON. No markdown. No text outside the JSON object.`; diff --git a/apps/simulator/src/prompts/graphEngine.ts b/apps/simulator/src/prompts/graphEngine.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd30b94931e48cc41e80dedf4862082a6fe80bc9 --- /dev/null +++ b/apps/simulator/src/prompts/graphEngine.ts @@ -0,0 +1,81 @@ +/** + * Graph Engine System Prompt — UPGRADED + * Role: Build Internal Representation (IR) graph from architecture plan + * Strict: ONLY registry node types, real expressions, complete data flow + */ +export const GRAPH_ENGINE_PROMPT = `You are a workflow graph compiler. Your job is to build a WorkflowGraph intermediate representation (IR) from an architecture plan. + +The graph is the single source of truth before compilation to n8n JSON. + +STRICT NODE TYPE RULES: +- You MUST use ONLY n8nNodeType values from the AVAILABLE NODE TYPES list provided +- NEVER invent node types. NEVER use "custom", "?", or placeholder types +- If you need an HTTP call, use: n8n-nodes-base.httpRequest +- If you need AI, use: @n8n/n8n-nodes-langchain.agent (with sub-nodes for model/memory) +- If you need Telegram, use: n8n-nodes-base.telegram (send) or n8n-nodes-base.telegramTrigger (receive) + +GRAPH STRUCTURE RULES: +1. Every node MUST have a valid n8nNodeType from the registry +2. Every non-trigger node MUST have at least one incoming edge +3. Trigger node MUST be at layer "trigger" and position {x: 0, y: 300} +4. Positions must form a clean left-to-right layout (x increases by 220 per level) +5. DataContracts MUST define input/output JSON fields with real field names +6. RetryPolicy MUST be set for all nodes where isCritical: true +7. Fallback paths MUST be defined for critical nodes + +DATA CONTRACT RULES: +- inputs: list of JSON fields this node expects from previous node +- outputs: list of JSON fields this node produces for next node +- Use real field names matching actual n8n node output (e.g., message, body, text, chatId) + +EXPRESSION REQUIREMENTS: +- keyParameters MUST use real n8n expressions: + - {{$json?.field ?? "default"}} — safe field access with default + - {{$node["NodeName"].json?.field ?? ""}} — cross-node reference + - {{$json?.items?.length > 0 ? "yes" : "no"}} — conditional +- NEVER use static values in parameter fields that should be dynamic + +RETURN this exact JSON structure: +{ + "workflowId": "wf-uuid-here", + "name": "Workflow Name", + "nodes": [ + { + "id": "node-uuid", + "label": "Display Name", + "n8nNodeType": "exact.node.type.from.registry", + "layer": "trigger|validation|transform|ai|integration|control|database|utility|monitoring", + "description": "What this node does specifically", + "isCritical": true/false, + "position": { "x": 0, "y": 300 }, + "retryPolicy": { "maxRetries": 3, "retryDelayMs": 1000, "retryOn": ["timeout", "5xx"] }, + "fallbackNodeId": "node-id-or-null", + "dataContract": { + "inputs": ["field1", "field2"], + "outputs": ["outputField1", "outputField2"] + }, + "keyParameters": { + "paramName": "={{$json?.field ?? ''}}" + } + } + ], + "edges": [ + { + "id": "edge-uuid", + "sourceNodeId": "source-node-id", + "targetNodeId": "target-node-id", + "outputIndex": 0, + "inputIndex": 0, + "label": "on success" + } + ], + "metadata": { + "domain": "workflow domain", + "version": "2.0.0", + "estimatedNodes": number, + "estimatedDuration": "per execution estimate e.g. 2-5s", + "riskLevel": "low|medium|high|critical" + } +} + +CRITICAL: Return ONLY valid JSON. No markdown. No text outside JSON.`; diff --git a/apps/simulator/src/prompts/intentInterpreter.ts b/apps/simulator/src/prompts/intentInterpreter.ts new file mode 100644 index 0000000000000000000000000000000000000000..984f1a91ee41053c5a7b248f58fdf169bada716b --- /dev/null +++ b/apps/simulator/src/prompts/intentInterpreter.ts @@ -0,0 +1,51 @@ +/** + * Intent Interpreter System Prompt — UPGRADED + * Role: Understand workflow requests deeply like a senior workflow engineer + * Now includes triggerType detection for Webhook Auto-Bind system + */ +export const INTENT_INTERPRETER_PROMPT = `You are a senior workflow engineer and systems architect specializing in n8n automation platforms. + +Your job is to deeply understand a user's workflow request and produce a structured analysis. + +You must think like: +- A systems architect who identifies integrations, dependencies, and risks +- A DevOps engineer who identifies reliability and scaling concerns +- A security engineer who identifies credential and safety requirements + +ANALYSIS FRAMEWORK: +1. Identify the workflow domain: sales_automation | ai_workflow | data_pipeline | webhook_system | notification | crm_integration | devops | ecommerce | general +2. Identify all external integrations (e.g. Telegram, OpenAI, Gmail, Slack, Airtable, Notion, GitHub, Stripe, HubSpot, Postgres) +3. Assess risk level: low (simple CRUD) | medium (external APIs, AI) | high (financial, critical data) | critical (multi-system with irreversible effects) +4. Determine sync vs async: sync = real-time response required | async = fire-and-forget background | hybrid = both +5. Identify if AI/LLM processing is required +6. Identify scaling requirements based on expected volume +7. List all identified risks: API timeouts, credential expiry, webhook duplication, AI output unreliability, rate limits, data loss +8. Assess complexity: simple (1-3 nodes) | moderate (4-8 nodes) | complex (9+ nodes or sub-workflows) +9. Determine if human approval is required before activation +10. CRITICAL — Detect the trigger type from the request: + - "telegram" → n8n-nodes-base.telegramTrigger + - "slack" → n8n-nodes-base.slackTrigger + - "email" / "inbox" / "imap" → n8n-nodes-base.emailReadImap + - "github" / "push" / "pull request" → n8n-nodes-base.githubTrigger + - "notion" → n8n-nodes-base.notionTrigger + - "schedule" / "cron" / "daily" / "hourly" / "every X" → n8n-nodes-base.scheduleTrigger + - "webhook" / "api call" / "http request" / "form submission" → n8n-nodes-base.webhook + - "manual" / "test" → n8n-nodes-base.manualTrigger + +RETURN this exact JSON structure: +{ + "workflowType": "string - descriptive type name e.g. 'Telegram AI Customer Support Bot'", + "requiresAI": boolean, + "integrations": ["array", "of", "service", "names"], + "riskLevel": "low|medium|high|critical", + "requiresHumanApproval": boolean, + "syncVsAsync": "sync|async|hybrid", + "scalingRequirements": "low|medium|high", + "identifiedRisks": ["risk description 1", "risk description 2"], + "estimatedComplexity": "simple|moderate|complex", + "domain": "one of the domain types above", + "triggerType": "telegram|slack|email|github|notion|schedule|webhook|manual", + "triggerHint": "brief explanation of why this trigger was detected" +} + +CRITICAL: Return ONLY valid JSON. No markdown. No explanation text.`; diff --git a/apps/simulator/src/prompts/simulator.ts b/apps/simulator/src/prompts/simulator.ts new file mode 100644 index 0000000000000000000000000000000000000000..e893ab896d10c2f00e6f9ddd55a24807e2927261 --- /dev/null +++ b/apps/simulator/src/prompts/simulator.ts @@ -0,0 +1,103 @@ +/** + * Simulator System Prompt — UPGRADED + * Role: Simulate n8n workflow execution with mock data + * Strict: trace every node, detect real failure points, chaos testing + */ +export const SIMULATOR_PROMPT = `You are an n8n workflow execution simulator. Your job is to dry-run a workflow and predict exactly how it will behave in production. + +You are NOT executing the workflow — you are SIMULATING it step by step with provided mock data. + +SIMULATION FRAMEWORK: + +For each node in the workflow (in execution order): +1. Show the INPUT data (what enters the node) +2. Apply the node's logic/transformation +3. Show the OUTPUT data (what exits the node) +4. Flag any potential issues (null references, missing fields, type errors) +5. Rate the step: "success" | "warning" | "failure" + +CHAOS TESTING REQUIREMENTS: +Simulate these failure scenarios: +- "timeout" — external API takes >10s to respond +- "null_data" — upstream node returns null/empty +- "malformed_input" — input has wrong types/missing required fields +- "expired_credentials" — credential token is invalid/expired +- "rate_limit" — external API returns 429 Too Many Requests +- "network_error" — connection refused or DNS failure + +For each chaos test: +- Was the node resilient? (retryOnFail, error handling) +- What happens downstream if this node fails? +- Is there a fallback path? + +SCORING CRITERIA (readinessScore 0-100): +- 100: All nodes execute correctly, all chaos tests handled, full error coverage +- 80-99: Minor warnings, good error handling, no critical failures +- 60-79: Some warnings, basic error handling, recoverable failures +- 40-59: Multiple warnings, poor error handling, some unrecoverable failures +- 0-39: Critical failures, empty nodes, broken expressions, no error handling + +readinessScore FORMULA: +- Base: 70 (assume basic workflow works) +- +15 if all expressions are safe (optional chaining) +- +10 if all critical nodes have retryOnFail +- +5 if error handling paths exist +- -20 per critical failure point +- -10 per unhandled null reference risk +- -5 per missing retry on external node + +DEPLOYMENT BLOCKERS (must be fixed before deployment): +- Any node with empty parameters +- Any broken expression that would throw at runtime +- Unknown node types +- Missing required credentials +- active: true (must always be false) + +RETURN this exact JSON structure: +{ + "passed": boolean, + "readinessScore": number (0-100), + "executionTrace": [ + { + "stepNumber": 1, + "nodeId": "uuid", + "nodeName": "Display Name", + "nodeType": "n8n-nodes-base.xxx", + "status": "success|warning|failure", + "inputData": { ... mock input json ... }, + "outputData": { ... simulated output json ... }, + "executionTimeMs": number, + "issues": ["issue description if any"], + "notes": "what happened in this step" + } + ], + "predictedFailurePoints": [ + { + "nodeId": "uuid", + "nodeName": "Name", + "failureType": "null_reference|timeout|credential_error|expression_error|missing_field|type_error", + "probability": "low|medium|high", + "description": "exactly what will fail and why", + "mitigation": "how to fix or handle this" + } + ], + "chaosTestResults": [ + { + "scenario": "timeout|null_data|malformed_input|expired_credentials|rate_limit|network_error", + "passed": boolean, + "affectedNode": "node name", + "description": "what happens during this scenario", + "resilient": boolean, + "fallbackActivated": boolean + } + ], + "riskAnalysis": { + "overallRisk": "low|medium|high|critical", + "risks": ["risk description 1", "risk description 2"], + "mitigations": ["mitigation 1", "mitigation 2"] + }, + "deploymentBlockers": ["blocker 1 — must fix before deployment", "blocker 2"], + "recommendations": ["improvement suggestion 1", "improvement suggestion 2"] +} + +CRITICAL: Return ONLY valid JSON. No markdown. No text outside the JSON object.`; diff --git a/apps/simulator/src/prompts/workflowPlanner.ts b/apps/simulator/src/prompts/workflowPlanner.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f3af52c3a3e3a1dc2c3204b5125a5c0e42ce42a --- /dev/null +++ b/apps/simulator/src/prompts/workflowPlanner.ts @@ -0,0 +1,80 @@ +/** + * Workflow Planner System Prompt — UPGRADED + * Role: Design production-grade n8n workflow architectures + * Senior engineer thinking: reliability, observability, error handling, scalability + */ +export const WORKFLOW_PLANNER_PROMPT = `You are a senior n8n workflow architect with 10+ years of production automation experience. + +Your job is to design complete, production-grade workflow architectures that a junior engineer can implement without guessing. + +ARCHITECTURE LAYERS (think in layers): +1. TRIGGER — The entry point (Webhook, Telegram, Schedule, Email, GitHub, etc.) +2. VALIDATION — Input validation (IF node, Code node checks) +3. TRANSFORM — Data preparation, normalization (SET, Code) +4. QUEUE / RATE LIMIT — Batch management (SplitInBatches, Wait) +5. PROCESSING / AI — Core business logic (HTTP, Code, AI Agent) +6. DECISION — Routing based on results (IF, Switch) +7. PERSISTENCE — Save results (Sheets, Airtable, Notion, Postgres, Airtable) +8. NOTIFICATION — Alerts, confirmations (Telegram, Slack, Gmail) +9. MONITORING — Success/failure tracking (logging nodes, Slack alerts) +10. ERROR HANDLING — Fallback paths, retry, stop-and-error + +DESIGN PRINCIPLES: +- NEVER design workflows with placeholder nodes ("Custom HTTP", "API Node", "Process Data") +- ALWAYS use real n8n node names from the registry +- EVERY external API call MUST have retry (retryOnFail: true, maxTries: 3) +- EVERY workflow MUST have a monitoring/notification layer +- EVERY branch MUST be handled (no dead paths) +- EVERY SET node MUST have explicit field mappings +- EVERY IF node MUST have real boolean conditions +- EVERY AI Agent MUST have proper system message and dynamic user input +- USE real n8n expressions: {{$json?.field ?? "default"}} format +- NEVER use empty field values, never use placeholder text + +RISK ANALYSIS REQUIREMENTS: +- List every external dependency +- List every credential required +- List every potential failure point +- Specify retry strategy for each critical node +- Recommend error workflow integration + +RETURN this exact JSON structure: +{ + "name": "Descriptive workflow name", + "description": "What this workflow does end-to-end", + "triggerNode": "exact n8n node type string", + "triggerDescription": "How this workflow is triggered", + "layers": [ + { + "layer": "trigger|validation|transform|queue|processing|ai|decision|persistence|notification|monitoring|error_handling", + "nodes": ["NodeDisplayName1", "NodeDisplayName2"], + "description": "What this layer does" + } + ], + "nodeList": [ + { + "displayName": "Node Display Name", + "n8nNodeType": "exact.node.type", + "layer": "layer name", + "purpose": "What this specific node does", + "isCritical": true/false, + "requiresCredential": "credential type or null", + "keyParameters": { "param": "value or expression" } + } + ], + "dataFlow": [ + "Step 1: Webhook receives POST with {message, userId}", + "Step 2: SET extracts message → {{$json?.body?.message ?? ''}}" + ], + "riskAnalysis": { + "overallRisk": "low|medium|high|critical", + "externalDependencies": ["Service1", "Service2"], + "requiredCredentials": ["credType1", "credType2"], + "failurePoints": ["possible failure 1"], + "retryStrategy": "description of retry approach" + }, + "estimatedNodes": number, + "complexity": "simple|moderate|complex" +} + +CRITICAL: Return ONLY valid JSON. No markdown. No text outside JSON.`; diff --git a/apps/simulator/src/routes/generate.ts b/apps/simulator/src/routes/generate.ts new file mode 100644 index 0000000000000000000000000000000000000000..064ff895b4c65e7177a5da236c471fb2bf0cc58b --- /dev/null +++ b/apps/simulator/src/routes/generate.ts @@ -0,0 +1,244 @@ +/** + * Simulator Generate Route — UPGRADED + * Full pipeline: Intent → Swarm Planning → Graph → Compile → Validate → Self-Heal → Simulate → Credential Analysis + * Provider-agnostic LLM Gateway, Swarm multi-agent, Self-Healing, Memory/Learning, Webhook Auto-Bind + */ +import { Router } from 'express'; +import { z } from 'zod'; +import { logger } from '../index'; +import { IntentInterpreterAgent } from '../agents/intentInterpreter'; +import { WorkflowGraphEngine } from '../agents/graphEngine'; +import { WorkflowCompiler } from '../agents/compiler'; +import { ValidationEngine } from '../agents/validator'; +import { DryRunSimulator } from '../agents/simulator'; +import { CredentialIntelligence } from '../agents/credentialIntelligence'; +import { QualityScorer } from '../services/qualityScorer'; +import { SwarmOrchestrator } from '../services/swarmOrchestrator'; +import { SelfHealingSystem } from '../services/selfHealing'; +import { webhookAutoBind } from '../services/webhookAutoBind'; +import { workflowMemory } from '../services/memorySystem'; +import { createRequestLLM } from '../services/llmClient'; +import type { GenerateWorkflowResponse } from '../types/workflow'; + +export const generateRoute: Router = Router(); + +const GenerateSchema = z.object({ + jobId: z.string(), + request: z.string().min(10), + options: z.object({ + skipSimulation: z.boolean().default(false), + requireApproval: z.boolean().default(true), + swarmSize: z.number().min(1).max(5).default(3), + skipSwarm: z.boolean().default(false), + skipSelfHealing: z.boolean().default(false), + }).default({}), + // LLM config — provider agnostic (apiKey for backward compat with CF Worker) + llmApiKey: z.string().optional(), + openaiApiKey: z.string().optional(), // legacy support + llmModel: z.string().optional(), + llmGatewayUrl: z.string().optional(), + llmProvider: z.string().optional(), + n8nBaseUrl: z.string(), + n8nApiKey: z.string(), +}); + +generateRoute.post('/', async (req, res) => { + const parsed = GenerateSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ success: false, error: 'Invalid request', details: parsed.error.flatten() }); + return; + } + + const { jobId, request, options, n8nBaseUrl, n8nApiKey } = parsed.data; + + // Resolve API key (support both new LLM_API_KEY and legacy OPENAI_API_KEY) + const apiKey = parsed.data.llmApiKey ?? parsed.data.openaiApiKey ?? process.env['LLM_API_KEY'] ?? process.env['OPENAI_API_KEY'] ?? ''; + if (!apiKey) { + res.status(400).json({ success: false, error: 'LLM API key not provided. Set llmApiKey in request or LLM_API_KEY env var.' }); + return; + } + + // Create provider-agnostic LLM gateway + const llm = createRequestLLM(apiKey, parsed.data.llmModel); + + logger.info({ jobId }, '[Generate] Starting upgraded pipeline (Swarm + Self-Healing + Memory)'); + + try { + // ─── Stage 1: Intent Interpretation ──────────────────────────────────── + logger.info({ jobId }, '[Generate] Stage 1: Intent Interpretation'); + const intentAgent = new IntentInterpreterAgent(llm); + const intent = await intentAgent.interpret(request); + + // ─── Stage 2: Webhook Auto-Bind trigger detection ─────────────────────── + logger.info({ jobId }, '[Generate] Stage 2: Webhook Auto-Bind trigger detection'); + const triggerDetection = webhookAutoBind.detectTrigger(request, intent); + intent.triggerType = triggerDetection.triggerType; + intent.triggerHint = `Auto-detected: ${triggerDetection.nodeType} (confidence: ${triggerDetection.confidence})`; + + // ─── Stage 3: Memory context retrieval ───────────────────────────────── + logger.info({ jobId }, '[Generate] Stage 3: Memory context retrieval'); + const memoryContext = workflowMemory.buildMemoryContext(intent.domain, intent.integrations); + + // Augment request with memory context for better planning + const augmentedRequest = memoryContext + ? `${request}\n\n${memoryContext}` + : request; + + // ─── Stage 4: Swarm Planning (parallel designs) or single plan ───────── + let finalPlan: import('../types/workflow').WorkflowArchitecturePlan; + let finalGraph: import('../types/workflow').WorkflowGraph; + let finalWorkflow: import('../types/workflow').N8nWorkflow; + let swarmResult: import('../services/swarmOrchestrator').SwarmResult | undefined; + + if (!options.skipSwarm && options.swarmSize > 1) { + logger.info({ jobId, swarmSize: options.swarmSize }, '[Generate] Stage 4: Swarm multi-agent planning'); + const swarm = new SwarmOrchestrator(llm); + swarmResult = await swarm.runSwarm(augmentedRequest, intent, n8nBaseUrl, n8nApiKey, options.swarmSize); + + finalPlan = swarmResult.winner.plan; + finalGraph = swarmResult.winner.graph; + finalWorkflow = swarmResult.winner.workflow; + } else { + // Single-agent path (faster, less thorough) + logger.info({ jobId }, '[Generate] Stage 4: Single-agent planning (swarm disabled)'); + const { WorkflowPlannerAgent } = await import('../agents/workflowPlanner'); + const planner = new WorkflowPlannerAgent(llm); + finalPlan = await planner.plan(augmentedRequest, intent); + + const graphEngine = new WorkflowGraphEngine(llm); + finalGraph = await graphEngine.buildGraph(augmentedRequest, intent, finalPlan); + + const compiler = new WorkflowCompiler(llm); + finalWorkflow = await compiler.compile(finalGraph, intent); + } + + // ─── Stage 5: Inject auto-configured trigger ──────────────────────────── + logger.info({ jobId }, '[Generate] Stage 5: Webhook Auto-Bind trigger injection'); + const triggerConfig = webhookAutoBind.buildTriggerConfig( + triggerDetection.nodeType, + request, + intent, + finalWorkflow.name ?? 'workflow', + ); + finalGraph = webhookAutoBind.injectTriggerIntoGraph(finalGraph, triggerConfig, triggerDetection.nodeType); + + // ─── Stage 6: Validation ──────────────────────────────────────────────── + logger.info({ jobId }, '[Generate] Stage 6: Validation'); + const validator = new ValidationEngine(llm, n8nBaseUrl, n8nApiKey); + let validationReport = await validator.validate(jobId, finalWorkflow, finalGraph); + + // ─── Stage 7: Self-Healing (if validation failed) ─────────────────────── + let healingResult: import('../services/selfHealing').HealingResult | undefined; + if (!validationReport.valid && !options.skipSelfHealing) { + logger.info({ jobId, errors: validationReport.issues.length }, '[Generate] Stage 7: Self-Healing'); + const healer = new SelfHealingSystem(llm); + healingResult = await healer.heal(finalWorkflow, finalGraph, validationReport); + + if (healingResult.healed && healingResult.healedWorkflow) { + finalWorkflow = healingResult.healedWorkflow; + // Re-validate after healing + validationReport = await validator.validate(jobId, finalWorkflow, finalGraph); + logger.info({ jobId, valid: validationReport.valid }, '[Generate] Re-validation after self-healing'); + } + } + + // ─── Stage 8: Dry-Run Simulation (unless skipped) ────────────────────── + let simulationReport: import('../types/workflow').SimulationReport | undefined; + if (!options.skipSimulation) { + logger.info({ jobId }, '[Generate] Stage 8: Dry-Run Simulation'); + const simulator = new DryRunSimulator(llm); + simulationReport = await simulator.simulate(jobId, finalWorkflow, finalGraph, finalPlan); + } + + // ─── Stage 9: Credential Analysis ────────────────────────────────────── + logger.info({ jobId }, '[Generate] Stage 9: Credential Analysis'); + const credentialIntel = new CredentialIntelligence(n8nBaseUrl, n8nApiKey); + const credentialAnalysis = await credentialIntel.analyse(jobId, finalWorkflow); + + // ─── Stage 10: Quality Scoring ────────────────────────────────────────── + const qualityScorer = new QualityScorer(); + const deploymentReadiness = qualityScorer.scoreDeploymentReadiness( + validationReport, + simulationReport, + credentialAnalysis, + ); + const qualityScore = qualityScorer.calculateQualityScore(validationReport, simulationReport); + + // ─── Stage 11: Memory storage ─────────────────────────────────────────── + const nodeTypes = finalWorkflow.nodes.map((n) => n.type); + if (validationReport.valid && (simulationReport?.passed ?? true)) { + workflowMemory.storeSuccessPattern( + jobId, + intent.domain, + intent.workflowType, + intent.integrations, + nodeTypes, + finalPlan.description ?? '', + qualityScore.overallScore, + validationReport.overallScore, + simulationReport?.readinessScore ?? 0, + ); + } else { + workflowMemory.storeFailurePattern( + jobId, + intent.domain, + intent.workflowType, + intent.integrations, + nodeTypes, + finalPlan.description ?? '', + validationReport.overallScore, + validationReport.issues.map((i) => i.message), + ); + } + + const finalState = (() => { + if (simulationReport?.passed && validationReport.valid) return 'simulated'; + if (validationReport.valid) return 'validated'; + return 'generated'; + })(); + + logger.info({ jobId, state: finalState, score: qualityScore.overallScore }, '[Generate] Pipeline complete'); + + const response: GenerateWorkflowResponse = { + success: true, + jobId, + state: finalState as GenerateWorkflowResponse['state'], + message: `Workflow generation pipeline complete. State: ${finalState}. Quality score: ${qualityScore.overallScore}/100`, + architecturePlan: finalPlan, + graph: finalGraph, + compiledWorkflow: finalWorkflow, + validationReport, + simulationReport, + credentialAnalysis, + deploymentReadiness, + qualityScore, + healingResult, + swarmResult: swarmResult ? { + swarmSize: swarmResult.swarmSize, + generatedAt: swarmResult.generatedAt, + winnerStrategy: swarmResult.winner.planningStrategy, + winnerScore: swarmResult.winner.qualityScore, + candidateScores: swarmResult.allCandidates.map((c) => ({ + id: c.id, + strategy: c.planningStrategy, + score: c.qualityScore, + })), + selectionReason: swarmResult.winner.selectionReason, + } : undefined, + triggerDetection: { + nodeType: triggerDetection.nodeType, + triggerType: triggerDetection.triggerType, + confidence: triggerDetection.confidence, + webhookPath: triggerConfig.webhookPath, + notes: triggerConfig.notes, + }, + memoryStats: workflowMemory.getStats(), + }; + + res.json(response); + } catch (err) { + const message = err instanceof Error ? err.message : 'Pipeline error'; + logger.error({ jobId, err }, '[Generate] Pipeline failed'); + res.status(500).json({ success: false, error: message, jobId }); + } +}); diff --git a/apps/simulator/src/routes/simulate.ts b/apps/simulator/src/routes/simulate.ts new file mode 100644 index 0000000000000000000000000000000000000000..68a3db943f6fa0d29bb3ce489cd4593eb816a79c --- /dev/null +++ b/apps/simulator/src/routes/simulate.ts @@ -0,0 +1,60 @@ +/** + * Simulator Simulate Route — UPGRADED + * Provider-agnostic dry-run simulation using LLM Gateway + */ +import { Router } from 'express'; +import { z } from 'zod'; +import { logger } from '../index'; +import { DryRunSimulator } from '../agents/simulator'; +import { createRequestLLM } from '../services/llmClient'; +import type { N8nWorkflow, WorkflowGraph, WorkflowArchitecturePlan } from '../types/workflow'; + +export const simulateRoute: Router = Router(); + +const SimulateSchema = z.object({ + jobId: z.string(), + workflow: z.record(z.unknown()), + graph: z.record(z.unknown()), + architecturePlan: z.record(z.unknown()).optional(), + mockData: z.record(z.unknown()).optional(), + llmApiKey: z.string().optional(), + openaiApiKey: z.string().optional(), + llmModel: z.string().optional(), +}); + +simulateRoute.post('/', async (req, res) => { + const parsed = SimulateSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ success: false, error: 'Invalid request', details: parsed.error.flatten() }); + return; + } + + const { jobId, workflow, graph, architecturePlan } = parsed.data; + const apiKey = parsed.data.llmApiKey ?? parsed.data.openaiApiKey ?? process.env['LLM_API_KEY'] ?? process.env['OPENAI_API_KEY'] ?? ''; + + logger.info({ jobId }, '[Simulate] Starting dry-run simulation'); + + try { + const llm = createRequestLLM(apiKey); + const simulator = new DryRunSimulator(llm); + + const simulationReport = await simulator.simulate( + jobId, + workflow as any as N8nWorkflow, + graph as any as WorkflowGraph, + (architecturePlan ?? {}) as any as WorkflowArchitecturePlan, + ); + + logger.info({ + jobId, + passed: simulationReport.passed, + score: simulationReport.readinessScore, + }, '[Simulate] Complete'); + + res.json({ success: true, simulationReport }); + } catch (err) { + const message = err instanceof Error ? err.message : 'Simulation error'; + logger.error({ jobId, err }, '[Simulate] Failed'); + res.status(500).json({ success: false, error: message, jobId }); + } +}); diff --git a/apps/simulator/src/routes/validate.ts b/apps/simulator/src/routes/validate.ts new file mode 100644 index 0000000000000000000000000000000000000000..504c620cf1a4b9c188d0585fca2ed5ecbf2fe8f0 --- /dev/null +++ b/apps/simulator/src/routes/validate.ts @@ -0,0 +1,61 @@ +/** + * Simulator Validate Route — UPGRADED + * Provider-agnostic validation using LLM Gateway + * Full: Schema + NodeRegistry + Graph + Expression + DataFlow + Reliability + Credential + */ +import { Router } from 'express'; +import { z } from 'zod'; +import { logger } from '../index'; +import { ValidationEngine } from '../agents/validator'; +import { createRequestLLM } from '../services/llmClient'; +import type { N8nWorkflow, WorkflowGraph } from '../types/workflow'; + +export const validateRoute: Router = Router(); + +const ValidateSchema = z.object({ + jobId: z.string(), + workflow: z.record(z.unknown()), + graph: z.record(z.unknown()), + llmApiKey: z.string().optional(), + openaiApiKey: z.string().optional(), + llmModel: z.string().optional(), + n8nBaseUrl: z.string(), + n8nApiKey: z.string(), + final: z.boolean().optional().default(false), +}); + +validateRoute.post('/', async (req, res) => { + const parsed = ValidateSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ success: false, error: 'Invalid request', details: parsed.error.flatten() }); + return; + } + + const { jobId, workflow, graph, n8nBaseUrl, n8nApiKey, final: isFinal } = parsed.data; + const apiKey = parsed.data.llmApiKey ?? parsed.data.openaiApiKey ?? process.env['LLM_API_KEY'] ?? process.env['OPENAI_API_KEY'] ?? ''; + + logger.info({ jobId, final: isFinal }, '[Validate] Starting validation'); + + try { + const llm = createRequestLLM(apiKey); + const validator = new ValidationEngine(llm, n8nBaseUrl, n8nApiKey); + const validationReport = await validator.validate( + jobId, + workflow as any as N8nWorkflow, + graph as any as WorkflowGraph, + ); + + logger.info({ + jobId, + valid: validationReport.valid, + score: validationReport.overallScore, + errors: validationReport.issues.length, + }, '[Validate] Complete'); + + res.json({ success: true, validationReport }); + } catch (err) { + const message = err instanceof Error ? err.message : 'Validation error'; + logger.error({ jobId, err }, '[Validate] Failed'); + res.status(500).json({ success: false, error: message, jobId }); + } +}); diff --git a/apps/simulator/src/services/llmClient.ts b/apps/simulator/src/services/llmClient.ts new file mode 100644 index 0000000000000000000000000000000000000000..d85b02ef99fedd3dc5c023a4252acda179bdce66 --- /dev/null +++ b/apps/simulator/src/services/llmClient.ts @@ -0,0 +1,69 @@ +/** + * LLM Client Factory for Simulator Service + * Provides a configured LLMGateway instance from process.env + * Completely removes direct OpenAI SDK dependency + */ +import { LLMGateway, type LLMGatewayConfig } from '@wfo/integrations/llm-providers/index'; + +let _instance: LLMGateway | null = null; + +export function getSimulatorLLM(): LLMGateway { + if (_instance) return _instance; + + const env = process.env; + const provider = (env['LLM_PROVIDER'] ?? 'openai') as LLMGatewayConfig['primary']['provider']; + const baseUrl = env['LLM_GATEWAY_URL'] ?? 'https://api.openai.com/v1'; + const apiKey = env['LLM_API_KEY'] ?? env['OPENAI_API_KEY'] ?? ''; + const defaultModel = env['LLM_MODEL'] ?? 'gpt-4o'; + + if (!apiKey) { + throw new Error( + 'LLM API key not configured. Set LLM_API_KEY (or OPENAI_API_KEY for legacy) in environment.', + ); + } + + const config: LLMGatewayConfig = { + primary: { provider, baseUrl, apiKey, defaultModel }, + defaultRetries: 3, + defaultRetryDelayMs: 1000, + defaultTimeoutMs: 90000, + }; + + // Optional fallback provider + const fallbackUrl = env['LLM_FALLBACK_URL']; + const fallbackKey = env['LLM_FALLBACK_KEY']; + const fallbackModel = env['LLM_FALLBACK_MODEL'] ?? defaultModel; + if (fallbackUrl && fallbackKey) { + config.fallback = { + provider: 'openai-compatible', + baseUrl: fallbackUrl, + apiKey: fallbackKey, + defaultModel: fallbackModel, + }; + } + + _instance = new LLMGateway(config); + return _instance; +} + +/** + * Create a per-request LLM client from a provided API key + * Used when the API key is passed per-request (from CF Worker) + */ +export function createRequestLLM(apiKey: string, model?: string): LLMGateway { + const env = process.env; + const provider = (env['LLM_PROVIDER'] ?? 'openai') as LLMGatewayConfig['primary']['provider']; + const baseUrl = env['LLM_GATEWAY_URL'] ?? 'https://api.openai.com/v1'; + + return new LLMGateway({ + primary: { + provider, + baseUrl, + apiKey, + defaultModel: model ?? env['LLM_MODEL'] ?? 'gpt-4o', + }, + defaultRetries: 3, + defaultRetryDelayMs: 1000, + defaultTimeoutMs: 90000, + }); +} diff --git a/apps/simulator/src/services/memorySystem.ts b/apps/simulator/src/services/memorySystem.ts new file mode 100644 index 0000000000000000000000000000000000000000..3c3762026bd516936c29c393c4b31b225a7828c4 --- /dev/null +++ b/apps/simulator/src/services/memorySystem.ts @@ -0,0 +1,333 @@ +/** + * Memory / Learning System — NEW + * Stores successful workflow patterns, failed patterns, high-performing architectures + * Influences node selection, planning, retry strategy, error prevention + * In-process store (can be extended with Redis/KV for persistence) + */ + +export interface WorkflowPattern { + id: string; + type: 'success' | 'failure'; + domain: string; + workflowType: string; + integrations: string[]; + nodeTypes: string[]; + architectureDescription: string; + qualityScore: number; + validationScore: number; + simulationScore: number; + keyLessons: string[]; + antiPatterns?: string[]; + createdAt: string; + useCount: number; +} + +export interface ArchitectureTemplate { + id: string; + name: string; + domain: string; + triggers: string[]; + nodeSequence: string[]; + avgQualityScore: number; + successRate: number; + description: string; + usageCount: number; +} + +// ─── In-Process Memory Store ────────────────────────────────────────────────── +class MemoryStore { + private patterns: Map = new Map(); + private templates: Map = new Map(); + private fixStrategies: Map = new Map(); + + // ─── Initialize with built-in high-performing templates ────────────────── + constructor() { + this.seedBuiltinTemplates(); + } + + // ─── Pattern Management ─────────────────────────────────────────────────── + + storeSuccessPattern( + jobId: string, + domain: string, + workflowType: string, + integrations: string[], + nodeTypes: string[], + architectureDescription: string, + qualityScore: number, + validationScore: number, + simulationScore: number, + ): void { + const id = `success-${jobId}`; + this.patterns.set(id, { + id, + type: 'success', + domain, + workflowType, + integrations, + nodeTypes, + architectureDescription, + qualityScore, + validationScore, + simulationScore, + keyLessons: this.extractLessons('success', nodeTypes, qualityScore), + createdAt: new Date().toISOString(), + useCount: 0, + }); + this.updateTemplate(domain, nodeTypes, qualityScore, true); + } + + storeFailurePattern( + jobId: string, + domain: string, + workflowType: string, + integrations: string[], + nodeTypes: string[], + architectureDescription: string, + qualityScore: number, + failureReasons: string[], + ): void { + const id = `failure-${jobId}`; + this.patterns.set(id, { + id, + type: 'failure', + domain, + workflowType, + integrations, + nodeTypes, + architectureDescription, + qualityScore, + validationScore: 0, + simulationScore: 0, + keyLessons: [], + antiPatterns: failureReasons, + createdAt: new Date().toISOString(), + useCount: 0, + }); + this.updateTemplate(domain, nodeTypes, qualityScore, false); + } + + storeFixStrategy(issuePattern: string, fixDescription: string): void { + const key = this.normalizeKey(issuePattern); + this.fixStrategies.set(key, fixDescription); + } + + // ─── Memory Retrieval ───────────────────────────────────────────────────── + + getRelevantPatterns(domain: string, integrations: string[]): WorkflowPattern[] { + return [...this.patterns.values()] + .filter((p) => + p.domain === domain || + integrations.some((i) => p.integrations.includes(i)), + ) + .sort((a, b) => b.qualityScore - a.qualityScore) + .slice(0, 5); + } + + getHighPerformingTemplates(domain: string): ArchitectureTemplate[] { + return [...this.templates.values()] + .filter((t) => t.domain === domain || t.domain === 'general') + .sort((a, b) => b.avgQualityScore - a.avgQualityScore) + .slice(0, 3); + } + + getSuccessPatterns(): WorkflowPattern[] { + return [...this.patterns.values()] + .filter((p) => p.type === 'success' && p.qualityScore >= 80) + .sort((a, b) => b.qualityScore - a.qualityScore) + .slice(0, 10); + } + + getAntiPatterns(): string[] { + return [...this.patterns.values()] + .filter((p) => p.type === 'failure') + .flatMap((p) => p.antiPatterns ?? []) + .filter((p, i, arr) => arr.indexOf(p) === i) // unique + .slice(0, 20); + } + + getFixStrategy(issuePattern: string): string | undefined { + return this.fixStrategies.get(this.normalizeKey(issuePattern)); + } + + /** + * Build memory context string for LLM injection + */ + buildMemoryContext(domain: string, integrations: string[]): string { + const patterns = this.getRelevantPatterns(domain, integrations); + const templates = this.getHighPerformingTemplates(domain); + const antiPatterns = this.getAntiPatterns(); + + const lines: string[] = []; + + if (patterns.length > 0) { + lines.push('=== SUCCESSFUL WORKFLOW PATTERNS FROM MEMORY ==='); + patterns + .filter((p) => p.type === 'success') + .slice(0, 3) + .forEach((p) => { + lines.push(`- Domain: ${p.domain}, Integrations: ${p.integrations.join(', ')}, Score: ${p.qualityScore}/100`); + lines.push(` Architecture: ${p.architectureDescription}`); + lines.push(` Nodes: ${p.nodeTypes.join(' → ')}`); + lines.push(` Lessons: ${p.keyLessons.join('; ')}`); + }); + } + + if (templates.length > 0) { + lines.push('\n=== HIGH-PERFORMING ARCHITECTURE TEMPLATES ==='); + templates.forEach((t) => { + lines.push(`- ${t.name}: ${t.description}`); + lines.push(` Node Sequence: ${t.nodeSequence.join(' → ')}`); + lines.push(` Avg Score: ${t.avgQualityScore}/100, Success Rate: ${Math.round(t.successRate * 100)}%`); + }); + } + + if (antiPatterns.length > 0) { + lines.push('\n=== KNOWN ANTI-PATTERNS TO AVOID ==='); + antiPatterns.slice(0, 5).forEach((ap) => lines.push(`- AVOID: ${ap}`)); + } + + return lines.length > 0 ? lines.join('\n') : ''; + } + + getStats(): { + totalPatterns: number; + successPatterns: number; + failurePatterns: number; + templates: number; + avgSuccessScore: number; + fixStrategies: number; + } { + const all = [...this.patterns.values()]; + const successes = all.filter((p) => p.type === 'success'); + const avgScore = successes.length > 0 + ? Math.round(successes.reduce((sum, p) => sum + p.qualityScore, 0) / successes.length) + : 0; + + return { + totalPatterns: all.length, + successPatterns: successes.length, + failurePatterns: all.filter((p) => p.type === 'failure').length, + templates: this.templates.size, + avgSuccessScore: avgScore, + fixStrategies: this.fixStrategies.size, + }; + } + + // ─── Private helpers ────────────────────────────────────────────────────── + + private updateTemplate(domain: string, nodeTypes: string[], score: number, success: boolean): void { + const key = `${domain}-${nodeTypes.slice(0, 3).join('-')}`; + const existing = this.templates.get(key); + + if (existing) { + const total = existing.usageCount + 1; + const successCount = success + ? Math.round(existing.successRate * existing.usageCount) + 1 + : Math.round(existing.successRate * existing.usageCount); + this.templates.set(key, { + ...existing, + avgQualityScore: Math.round((existing.avgQualityScore * existing.usageCount + score) / total), + successRate: successCount / total, + usageCount: total, + }); + } + } + + private extractLessons(type: string, nodeTypes: string[], score: number): string[] { + const lessons: string[] = []; + if (score >= 90) lessons.push('High-quality architecture with proper error handling'); + if (nodeTypes.includes('@n8n/n8n-nodes-langchain.agent')) { + lessons.push('AI Agent with proper system message and dynamic user input'); + } + if (nodeTypes.includes('n8n-nodes-base.splitInBatches')) { + lessons.push('Batch processing with SplitInBatches for array handling'); + } + return lessons; + } + + private normalizeKey(text: string): string { + return text.toLowerCase().replace(/[^a-z0-9]/g, '-').slice(0, 80); + } + + // ─── Built-in high-performing templates ─────────────────────────────────── + private seedBuiltinTemplates(): void { + const builtins: ArchitectureTemplate[] = [ + { + id: 'telegram-ai-bot', + name: 'Telegram AI Bot', + domain: 'ai_workflow', + triggers: ['n8n-nodes-base.telegramTrigger'], + nodeSequence: [ + 'n8n-nodes-base.telegramTrigger', + 'n8n-nodes-base.set', + '@n8n/n8n-nodes-langchain.agent', + 'n8n-nodes-base.set', + 'n8n-nodes-base.telegram', + ], + avgQualityScore: 92, + successRate: 0.95, + description: 'Telegram bot with AI Agent response — SET for input prep, AI Agent, SET for output formatting, Telegram send', + usageCount: 0, + }, + { + id: 'webhook-to-integration', + name: 'Webhook → Validate → Process → Notify', + domain: 'webhook_system', + triggers: ['n8n-nodes-base.webhook'], + nodeSequence: [ + 'n8n-nodes-base.webhook', + 'n8n-nodes-base.if', + 'n8n-nodes-base.set', + 'n8n-nodes-base.httpRequest', + 'n8n-nodes-base.slack', + 'n8n-nodes-base.respondToWebhook', + ], + avgQualityScore: 88, + successRate: 0.92, + description: 'Webhook trigger → validate input with IF → SET fields → call external API → notify Slack → respond to caller', + usageCount: 0, + }, + { + id: 'scheduled-data-pipeline', + name: 'Scheduled Data Pipeline', + domain: 'data_pipeline', + triggers: ['n8n-nodes-base.scheduleTrigger'], + nodeSequence: [ + 'n8n-nodes-base.scheduleTrigger', + 'n8n-nodes-base.httpRequest', + 'n8n-nodes-base.code', + 'n8n-nodes-base.splitInBatches', + 'n8n-nodes-base.set', + 'n8n-nodes-base.googleSheets', + ], + avgQualityScore: 87, + successRate: 0.90, + description: 'Cron trigger → fetch data via HTTP → transform with Code → batch process → SET fields → store to Sheets', + usageCount: 0, + }, + { + id: 'email-to-crm', + name: 'Email Trigger → CRM Update', + domain: 'crm_integration', + triggers: ['n8n-nodes-base.emailReadImap'], + nodeSequence: [ + 'n8n-nodes-base.emailReadImap', + 'n8n-nodes-base.set', + 'n8n-nodes-base.if', + 'n8n-nodes-base.hubspot', + 'n8n-nodes-base.slack', + ], + avgQualityScore: 85, + successRate: 0.88, + description: 'Email trigger → extract fields → IF condition → update HubSpot → notify team via Slack', + usageCount: 0, + }, + ]; + + builtins.forEach((t) => this.templates.set(t.id, t)); + } +} + +// ─── Singleton export ───────────────────────────────────────────────────────── +export const workflowMemory = new MemoryStore(); diff --git a/apps/simulator/src/services/mockDataGenerator.ts b/apps/simulator/src/services/mockDataGenerator.ts new file mode 100644 index 0000000000000000000000000000000000000000..3bce657a85db877bfdd9503ab59f19f834ca9a89 --- /dev/null +++ b/apps/simulator/src/services/mockDataGenerator.ts @@ -0,0 +1,61 @@ +/** + * Mock Data Generator Service — UPGRADED + * Generates realistic mock inputs for dry-run simulation + * Provider-agnostic LLM Gateway — NO direct OpenAI dependency + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { N8nWorkflow, WorkflowGraph } from '../types/workflow'; + +export class MockDataGenerator { + private llm: LLMGateway; + + constructor(llm: LLMGateway) { + this.llm = llm; + } + + async generate( + workflow: N8nWorkflow, + graph: WorkflowGraph, + ): Promise> { + const triggerNode = workflow.nodes.find((n) => + n.type.toLowerCase().includes('trigger') || + n.type.toLowerCase().includes('webhook'), + ); + + return this.llm.completeJSON>([ + { + role: 'system', + content: `You are a test data engineer. Generate realistic mock data for workflow simulation. +Always include multiple scenarios: normal, malformed, null_payload, partial_payload, edge_case. +Data must match the actual fields the workflow nodes expect based on the trigger type.`, + }, + { + role: 'user', + content: `Generate mock input data for this workflow's trigger node. + +TRIGGER NODE: ${JSON.stringify(triggerNode ?? workflow.nodes[0], null, 2)} +WORKFLOW NAME: ${workflow.name} +WORKFLOW DOMAIN: ${graph.metadata.domain} + +Return JSON: +{ + "normal": { ... realistic happy-path data matching trigger output ... }, + "malformed": { ... intentionally broken/wrong-type data ... }, + "null_payload": null, + "partial_payload": { ... some required fields missing ... }, + "edge_case": { ... boundary conditions, very long strings, unicode, empty arrays ... } +}`, + }, + ], { + temperature: 0.3, + retries: 2, + }).catch(() => ({ + normal: {}, + malformed: {}, + null_payload: null, + partial_payload: {}, + edge_case: {}, + error: 'Mock data generation failed — using empty defaults', + })); + } +} diff --git a/apps/simulator/src/services/qualityScorer.ts b/apps/simulator/src/services/qualityScorer.ts new file mode 100644 index 0000000000000000000000000000000000000000..da6f3916dc684a86d3b820e00f278c61d4da11f3 --- /dev/null +++ b/apps/simulator/src/services/qualityScorer.ts @@ -0,0 +1,155 @@ +/** + * Quality Scorer Service — UPGRADED + * Evaluates workflow quality across multiple dimensions + * Produces deployment readiness score with detailed breakdown + */ +import type { + ValidationReport, + SimulationReport, + CredentialAnalysis, + DeploymentReadiness, + WorkflowQualityScore, +} from '../types/workflow'; + +export class QualityScorer { + scoreDeploymentReadiness( + validation: ValidationReport, + simulation: SimulationReport | undefined, + credentials: CredentialAnalysis, + ): DeploymentReadiness { + const blockingIssues: string[] = []; + const recommendations: string[] = []; + + // Gate 1: Validation + if (!validation.valid) { + const errorCount = validation.issues.length; + blockingIssues.push(`Validation failed with ${errorCount} error(s): ${validation.issues.map((i) => i.message).slice(0, 3).join('; ')}`); + } + + // Gate 2: Node Registry (unknown nodes = blocking) + if (validation.nodeRegistryValidation && !validation.nodeRegistryValidation.passed) { + const unknownErrors = validation.nodeRegistryValidation.issues.filter((i) => i.severity === 'error'); + if (unknownErrors.length > 0) { + blockingIssues.push(`${unknownErrors.length} unknown/invalid node type(s) detected — must use only registered n8n node types`); + } + } + + // Gate 3: Simulation + if (simulation && !simulation.passed) { + blockingIssues.push(`Dry-run simulation failed (readiness: ${simulation.readinessScore}/100)`); + } + + // Gate 4: Deployment blockers from simulation + if (simulation?.deploymentBlockers && simulation.deploymentBlockers.length > 0) { + simulation.deploymentBlockers.slice(0, 3).forEach((blocker) => { + blockingIssues.push(`Simulation blocker: ${blocker}`); + }); + } + + // Gate 5: Credentials (warning only — some deployments don't have n8n access) + if (!credentials.allCredentialsPresent && credentials.n8nReachable) { + const missingTypes = credentials.missing.map((m) => m.credentialType).join(', '); + blockingIssues.push(`Missing credentials in n8n: ${missingTypes}`); + } + + // Recommendations + if (validation.warnings.length > 0) { + recommendations.push(`Address ${validation.warnings.length} validation warning(s) for production-grade quality`); + } + if (simulation?.predictedFailurePoints && simulation.predictedFailurePoints.length > 0) { + recommendations.push(`Review ${simulation.predictedFailurePoints.length} predicted failure point(s)`); + } + if (simulation?.recommendations) { + recommendations.push(...simulation.recommendations.slice(0, 3)); + } + if (!credentials.allCredentialsPresent && !credentials.n8nReachable) { + recommendations.push('Verify all required credentials are configured in n8n before activation'); + } + + // Confidence score calculation + const validationScore = validation.overallScore; + const simulationScore = simulation?.readinessScore ?? 50; + const credentialScore = credentials.allCredentialsPresent ? 100 : (credentials.n8nReachable ? 0 : 50); + const nodeRegistryScore = validation.nodeRegistryValidation?.score ?? 100; + const dataFlowScore = validation.dataFlowValidation?.score ?? 100; + + const confidenceScore = Math.round( + validationScore * 0.30 + + simulationScore * 0.30 + + credentialScore * 0.15 + + nodeRegistryScore * 0.15 + + dataFlowScore * 0.10, + ); + + const riskLevel = (() => { + if (confidenceScore >= 85) return 'low' as const; + if (confidenceScore >= 65) return 'medium' as const; + if (confidenceScore >= 40) return 'high' as const; + return 'critical' as const; + })(); + + return { + deploymentReady: blockingIssues.length === 0, + confidenceScore, + riskLevel, + blockingIssues, + recommendations, + }; + } + + calculateQualityScore( + validation: ValidationReport, + simulation: SimulationReport | undefined, + ): WorkflowQualityScore { + const reliabilityScore = validation.reliabilityValidation.score; + const observabilityScore = Math.min(100, reliabilityScore + 10); + const retryCoverage = Math.max(0, reliabilityScore - 10); + const expressionSafety = validation.expressionValidation.score; + const credentialReadiness = validation.credentialValidation.score; + const branchingQuality = validation.graphValidation.score; + const deploymentSafety = validation.schemaValidation.score; + const nodeRegistryScore = validation.nodeRegistryValidation?.score ?? 100; + const dataFlowScore = validation.dataFlowValidation?.score ?? 100; + + const maintainabilityScore = Math.round((branchingQuality + deploymentSafety + nodeRegistryScore) / 3); + + const overallScore = Math.round( + reliabilityScore * 0.15 + + expressionSafety * 0.15 + + credentialReadiness * 0.10 + + branchingQuality * 0.10 + + deploymentSafety * 0.10 + + nodeRegistryScore * 0.20 + + dataFlowScore * 0.10 + + observabilityScore * 0.10, + ); + + const riskLevel = overallScore >= 85 ? 'low' as const + : overallScore >= 65 ? 'medium' as const + : overallScore >= 40 ? 'high' as const + : 'critical' as const; + + return { + overallScore, + reliabilityScore, + maintainabilityScore, + observabilityScore, + retryCoverage, + expressionSafety, + credentialReadiness, + branchingQuality, + deploymentSafety, + riskLevel, + scoreExplanations: { + overall: `Overall quality: ${overallScore}/100 (${riskLevel} risk)`, + reliability: `Retry policies and fallback coverage: ${reliabilityScore}/100`, + maintainability: `Graph structure, naming, registry compliance: ${maintainabilityScore}/100`, + observability: `Logging and monitoring coverage: ${observabilityScore}/100`, + expressionSafety: `Expression null-safety (optional chaining): ${expressionSafety}/100`, + credentialReadiness: `Credential availability: ${credentialReadiness}/100`, + nodeRegistry: `Registry compliance (no unknown nodes): ${nodeRegistryScore}/100`, + dataFlow: `Data flow correctness (expressions, SET, IF, CODE): ${dataFlowScore}/100`, + }, + }; + } +} diff --git a/apps/simulator/src/services/selfHealing.ts b/apps/simulator/src/services/selfHealing.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5c4d7daaed8cd128619d931d4a97f572353fb79 --- /dev/null +++ b/apps/simulator/src/services/selfHealing.ts @@ -0,0 +1,377 @@ +/** + * Self-Healing System — NEW + * Detects root cause of workflow failures and applies corrective transformations + * Stores failure patterns in memory, prevents repeat failures + * Integrates with validation + simulation pipeline + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { ValidationReport, SimulationReport, N8nWorkflow, WorkflowGraph } from '../types/workflow'; +import { isValidNodeType } from '../knowledge/nodeRegistry'; + +export interface HealingAction { + type: 'replace_node' | 'fix_expression' | 'add_parameter' | 'fix_connection' | 'add_retry' | 'remove_node'; + targetNodeId?: string; + description: string; + applied: boolean; + before?: unknown; + after?: unknown; +} + +export interface HealingResult { + healed: boolean; + actionsApplied: HealingAction[]; + remainingIssues: string[]; + healedWorkflow?: N8nWorkflow; + confidence: number; // 0-100 +} + +export interface FailurePattern { + id: string; + pattern: string; + rootCause: string; + fixStrategy: string; + occurrences: number; + lastSeen: string; +} + +// In-memory failure store (persists across requests in a single process) +const failureMemory: Map = new Map(); + +export class SelfHealingSystem { + private llm: LLMGateway; + + constructor(llm: LLMGateway) { + this.llm = llm; + } + + /** + * Attempt to auto-heal a workflow based on validation and simulation reports + */ + async heal( + workflow: N8nWorkflow, + graph: WorkflowGraph, + validationReport: ValidationReport, + simulationReport?: SimulationReport, + ): Promise { + const actions: HealingAction[] = []; + let healedWorkflow = structuredClone(workflow); + + // ─── Phase 1: Rule-based deterministic healing ──────────────────────── + const ruleBasedActions = this.applyRuleBasedFixes(healedWorkflow, validationReport); + actions.push(...ruleBasedActions.actions); + healedWorkflow = ruleBasedActions.workflow; + + // ─── Phase 2: AI-assisted healing for complex issues ────────────────── + const complexIssues = validationReport.issues.filter((i) => + i.severity === 'error' && + !actions.some((a) => a.targetNodeId === i.nodeId && a.applied), + ); + + if (complexIssues.length > 0) { + try { + const aiHealing = await this.applyAIHealing(healedWorkflow, graph, complexIssues, simulationReport); + actions.push(...aiHealing.actions); + if (aiHealing.healedWorkflow) { + healedWorkflow = aiHealing.healedWorkflow; + } + } catch (err) { + console.warn('[SelfHealing] AI healing phase failed:', err); + } + } + + // ─── Phase 3: Record failure pattern ───────────────────────────────── + this.recordFailurePattern(validationReport, simulationReport, actions); + + const appliedCount = actions.filter((a) => a.applied).length; + const remainingErrors = validationReport.issues.filter((i) => + i.severity === 'error' && + !actions.some((a) => a.targetNodeId === i.nodeId && a.applied), + ); + + return { + healed: appliedCount > 0 && remainingErrors.length === 0, + actionsApplied: actions.filter((a) => a.applied), + remainingIssues: remainingErrors.map((i) => i.message), + healedWorkflow: appliedCount > 0 ? healedWorkflow : undefined, + confidence: Math.round((appliedCount / Math.max(1, actions.length)) * 100), + }; + } + + // ─── Rule-Based Fixes (deterministic, no LLM needed) ───────────────────── + private applyRuleBasedFixes( + workflow: N8nWorkflow, + report: ValidationReport, + ): { workflow: N8nWorkflow; actions: HealingAction[] } { + const actions: HealingAction[] = []; + + // Fix 1: active must be false + if ((workflow.active as any) === true) { + workflow.active = false; + actions.push({ + type: 'fix_expression', + description: 'Set active: false (safety rule — never auto-activate)', + applied: true, + before: true, + after: false, + }); + } + + // Fix 2: Missing node IDs → generate UUID-like IDs + workflow.nodes.forEach((node) => { + if (!node.id || node.id.trim() === '') { + const newId = generateId(); + actions.push({ + type: 'add_parameter', + targetNodeId: node.name, + description: `Generated missing ID for node "${node.name}"`, + applied: true, + before: node.id, + after: newId, + }); + node.id = newId; + } + }); + + // Fix 3: Missing typeVersion → set to 1 + workflow.nodes.forEach((node) => { + if (!node.typeVersion || node.typeVersion < 1) { + actions.push({ + type: 'add_parameter', + targetNodeId: node.id, + description: `Added missing typeVersion: 1 to node "${node.name}"`, + applied: true, + before: node.typeVersion, + after: 1, + }); + node.typeVersion = 1; + } + }); + + // Fix 4: Missing position → assign default grid positions + workflow.nodes.forEach((node, i) => { + if (!node.position || !Array.isArray(node.position) || node.position.length !== 2) { + const newPos: [number, number] = [i * 220, 300]; + actions.push({ + type: 'fix_expression', + targetNodeId: node.id, + description: `Set default position for node "${node.name}"`, + applied: true, + before: node.position, + after: newPos, + }); + node.position = newPos; + } + }); + + // Fix 5: Add retry to external nodes that support it + workflow.nodes.forEach((node) => { + const externalTypes = [ + 'n8n-nodes-base.httpRequest', + 'n8n-nodes-base.telegram', + 'n8n-nodes-base.slack', + 'n8n-nodes-base.gmail', + 'n8n-nodes-base.openAi', + '@n8n/n8n-nodes-langchain.agent', + 'n8n-nodes-base.googleSheets', + 'n8n-nodes-base.airtable', + 'n8n-nodes-base.notion', + 'n8n-nodes-base.postgres', + 'n8n-nodes-base.hubspot', + 'n8n-nodes-base.stripe', + 'n8n-nodes-base.github', + ]; + + if (externalTypes.includes(node.type) && !node.retryOnFail) { + actions.push({ + type: 'add_retry', + targetNodeId: node.id, + description: `Added retry policy to external node "${node.name}"`, + applied: true, + before: { retryOnFail: false }, + after: { retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 }, + }); + node.retryOnFail = true; + node.maxTries = node.maxTries ?? 3; + node.waitBetweenTries = node.waitBetweenTries ?? 1000; + } + }); + + // Fix 6: onError field default + workflow.nodes.forEach((node) => { + if (!node.onError) { + node.onError = 'continueErrorOutput'; + actions.push({ + type: 'add_parameter', + targetNodeId: node.id, + description: `Set onError: continueErrorOutput for node "${node.name}"`, + applied: true, + before: undefined, + after: 'continueErrorOutput', + }); + } + }); + + // Fix 7: Remove nodes with unknown types from registry + const unknownNodes = workflow.nodes.filter((n) => n.type && !isValidNodeType(n.type)); + unknownNodes.forEach((node) => { + actions.push({ + type: 'remove_node', + targetNodeId: node.id, + description: `REJECTED unknown node type "${node.type}" for node "${node.name}" — not in registry`, + applied: true, + before: node.type, + after: null, + }); + }); + if (unknownNodes.length > 0) { + const unknownIds = new Set(unknownNodes.map((n) => n.id)); + workflow.nodes = workflow.nodes.filter((n) => !unknownIds.has(n.id)); + } + + // Fix 8: Ensure settings are complete + if (!workflow.settings) { + workflow.settings = {}; + actions.push({ + type: 'add_parameter', + description: 'Added missing workflow settings object', + applied: true, + before: undefined, + after: {}, + }); + } + if (!workflow.settings.executionOrder) { + workflow.settings.executionOrder = 'v1'; + actions.push({ + type: 'add_parameter', + description: 'Set settings.executionOrder: v1', + applied: true, + }); + } + + return { workflow, actions }; + } + + // ─── AI-Assisted Healing (for complex expression / logic issues) ────────── + private async applyAIHealing( + workflow: N8nWorkflow, + graph: WorkflowGraph, + issues: Array<{ message: string; nodeId?: string; suggestion: string; category: string }>, + simulationReport?: SimulationReport, + ): Promise<{ actions: HealingAction[]; healedWorkflow?: N8nWorkflow }> { + // Find the historical fix if available + const historicalFixes = this.getRelevantHistoricalFixes(issues.map((i) => i.message)); + + const result = await this.llm.completeJSON<{ + fixedWorkflow: N8nWorkflow; + actionsApplied: Array<{ nodeId?: string; description: string; type: string }>; + }>([ + { + role: 'system', + content: `You are an n8n workflow self-healing system. Your job is to fix broken workflows. +Fix ONLY the identified issues. Do not change anything else. +NEVER set active: true. +ALWAYS use real expressions like {{$json?.field ?? ""}}. +Return the complete fixed workflow JSON.`, + }, + { + role: 'user', + content: `Fix these workflow issues: + +CURRENT WORKFLOW: +${JSON.stringify(workflow, null, 2)} + +ISSUES TO FIX: +${issues.map((i, idx) => `${idx + 1}. [${i.category}] ${i.message}\n Fix: ${i.suggestion}`).join('\n')} + +${simulationReport ? `SIMULATION FAILURES:\n${JSON.stringify(simulationReport.predictedFailurePoints, null, 2)}` : ''} + +${historicalFixes.length > 0 ? `HISTORICAL FIX STRATEGIES:\n${historicalFixes.join('\n')}` : ''} + +Return JSON: +{ + "fixedWorkflow": { ... complete fixed n8n workflow ... }, + "actionsApplied": [{ "nodeId": "...", "description": "what was fixed", "type": "fix_type" }] +}`, + }, + ], { + temperature: 0.0, + retries: 2, + }); + + const actions: HealingAction[] = (result.actionsApplied ?? []).map((a) => ({ + type: a.type as HealingAction['type'], + targetNodeId: a.nodeId, + description: a.description, + applied: true, + })); + + return { + actions, + healedWorkflow: result.fixedWorkflow ? { + ...result.fixedWorkflow, + active: false, // Always enforce safety + } : undefined, + }; + } + + // ─── Failure Pattern Memory ─────────────────────────────────────────────── + private recordFailurePattern( + validation: ValidationReport, + simulation: SimulationReport | undefined, + actions: HealingAction[], + ): void { + validation.issues.forEach((issue) => { + const key = `${issue.category}-${issue.id}`; + const existing = failureMemory.get(key); + const action = actions.find((a) => a.targetNodeId === issue.nodeId); + + if (existing) { + failureMemory.set(key, { + ...existing, + occurrences: existing.occurrences + 1, + lastSeen: new Date().toISOString(), + fixStrategy: action?.description ?? existing.fixStrategy, + }); + } else { + failureMemory.set(key, { + id: key, + pattern: issue.message, + rootCause: issue.category, + fixStrategy: action?.description ?? issue.suggestion, + occurrences: 1, + lastSeen: new Date().toISOString(), + }); + } + }); + } + + private getRelevantHistoricalFixes(issueMessages: string[]): string[] { + const fixes: string[] = []; + failureMemory.forEach((pattern) => { + const isRelevant = issueMessages.some( + (msg) => msg.toLowerCase().includes(pattern.rootCause.toLowerCase()), + ); + if (isRelevant && pattern.occurrences > 1) { + fixes.push(`Pattern: "${pattern.pattern}" → Fix: "${pattern.fixStrategy}" (seen ${pattern.occurrences}x)`); + } + }); + return fixes.slice(0, 5); // Top 5 most relevant + } + + /** + * Get current failure memory stats (for reporting) + */ + getMemoryStats(): { totalPatterns: number; topFailures: FailurePattern[] } { + const patterns = [...failureMemory.values()]; + const sorted = patterns.sort((a, b) => b.occurrences - a.occurrences); + return { + totalPatterns: patterns.length, + topFailures: sorted.slice(0, 10), + }; + } +} + +function generateId(): string { + const hex = () => Math.floor(Math.random() * 0x10000).toString(16).padStart(4, '0'); + return `${hex()}${hex()}-${hex()}-${hex()}-${hex()}-${hex()}${hex()}${hex()}`; +} diff --git a/apps/simulator/src/services/swarmOrchestrator.ts b/apps/simulator/src/services/swarmOrchestrator.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa78459c9e9ddb4a2a28550f69fdbb6237eaeb1e --- /dev/null +++ b/apps/simulator/src/services/swarmOrchestrator.ts @@ -0,0 +1,228 @@ +/** + * Swarm Orchestrator — NEW + * Generates multiple workflow designs in parallel, selects the best via scoring + * Multi-agent swarm: each agent produces an independent design candidate + * Best candidate wins based on quality scoring + */ +import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; +import type { + WorkflowIntent, + WorkflowArchitecturePlan, + WorkflowGraph, + N8nWorkflow, + ValidationReport, + SimulationReport, +} from '../types/workflow'; +import { WorkflowPlannerAgent } from '../agents/workflowPlanner'; +import { WorkflowGraphEngine } from '../agents/graphEngine'; +import { WorkflowCompiler } from '../agents/compiler'; +import { ValidationEngine } from '../agents/validator'; +import { QualityScorer } from './qualityScorer'; + +export interface SwarmCandidate { + id: string; + plan: WorkflowArchitecturePlan; + graph: WorkflowGraph; + workflow: N8nWorkflow; + validationReport: ValidationReport; + qualityScore: number; + planningStrategy: string; + selectionReason?: string; +} + +export interface SwarmResult { + winner: SwarmCandidate; + allCandidates: SwarmCandidate[]; + swarmSize: number; + generatedAt: string; +} + +export class SwarmOrchestrator { + private llm: LLMGateway; + private qualityScorer: QualityScorer; + + constructor(llm: LLMGateway) { + this.llm = llm; + this.qualityScorer = new QualityScorer(); + } + + /** + * Run swarm: generate N parallel workflow designs, pick the best + * @param swarmSize Number of parallel designs (default: 3) + */ + async runSwarm( + userRequest: string, + intent: WorkflowIntent, + n8nBaseUrl: string, + n8nApiKey: string, + swarmSize = 3, + ): Promise { + const strategies = this.getSwarmStrategies(intent).slice(0, swarmSize); + + console.log(`[Swarm] Generating ${strategies.length} parallel workflow designs...`); + + // ─── Generate all candidates in parallel ───────────────────────────── + const candidateResults = await Promise.allSettled( + strategies.map((strategy, idx) => + this.generateCandidate( + `candidate-${idx + 1}`, + userRequest, + intent, + strategy, + n8nBaseUrl, + n8nApiKey, + ), + ), + ); + + const candidates: SwarmCandidate[] = candidateResults + .filter((r): r is PromiseFulfilledResult => r.status === 'fulfilled') + .map((r) => r.value); + + if (candidates.length === 0) { + throw new Error('Swarm: all candidates failed to generate. Check LLM connectivity.'); + } + + // ─── Score and rank all candidates ─────────────────────────────────── + const ranked = candidates + .sort((a, b) => b.qualityScore - a.qualityScore); + + const winner = ranked[0]!; + winner.selectionReason = this.buildSelectionReason(winner, ranked); + + console.log( + `[Swarm] Winner: ${winner.id} (score: ${winner.qualityScore}) from ${candidates.length} candidates`, + ); + + return { + winner, + allCandidates: ranked, + swarmSize: candidates.length, + generatedAt: new Date().toISOString(), + }; + } + + // ─── Generate a single candidate ───────────────────────────────────────── + private async generateCandidate( + id: string, + userRequest: string, + intent: WorkflowIntent, + strategy: { name: string; plannerHint: string; compilerHint: string }, + n8nBaseUrl: string, + n8nApiKey: string, + ): Promise { + // Each candidate uses the same LLM but with different strategy prompts + const planner = new WorkflowPlannerAgent(this.llm); + const graphEngine = new WorkflowGraphEngine(this.llm); + const compiler = new WorkflowCompiler(this.llm); + const validator = new ValidationEngine(this.llm, n8nBaseUrl, n8nApiKey); + + // Planner with strategy-specific hint injected into the request + const augmentedRequest = `${userRequest}\n\n[DESIGN STRATEGY: ${strategy.plannerHint}]`; + const plan = await planner.plan(augmentedRequest, intent); + const graph = await graphEngine.buildGraph(userRequest, intent, plan); + const workflow = await compiler.compile(graph, intent); + + // Run validation to score this candidate + const validationReport = await validator.validate(id, workflow, graph); + + // Calculate quality score + const qualityScore = this.scoreCandidate(validationReport, workflow, graph); + + return { + id, + plan, + graph, + workflow, + validationReport, + qualityScore, + planningStrategy: strategy.name, + }; + } + + // ─── Strategy definitions ───────────────────────────────────────────────── + private getSwarmStrategies(intent: WorkflowIntent): Array<{ + name: string; + plannerHint: string; + compilerHint: string; + }> { + const baseStrategies = [ + { + name: 'Reliability-First', + plannerHint: 'Focus on maximum reliability: retry policies on all external nodes, error handling on every branch, monitoring nodes, and fallback paths for every critical operation.', + compilerHint: 'Maximize retry policies, add error output branches, include monitoring nodes', + }, + { + name: 'Simplicity-First', + plannerHint: 'Focus on minimal node count with clean linear flow. Avoid over-engineering. Use the minimum necessary nodes to achieve the goal reliably.', + compilerHint: 'Keep node count minimal, prefer linear flow, avoid unnecessary branches', + }, + { + name: 'Observability-First', + plannerHint: 'Focus on full observability: add logging at every stage, notification nodes for success and failure, detailed SET nodes for data tracking, and comprehensive audit trail.', + compilerHint: 'Add monitoring nodes, notification steps, detailed logging with SET nodes', + }, + ]; + + // Add AI-specific strategy if needed + if (intent.requiresAI) { + baseStrategies.push({ + name: 'AI-Optimized', + plannerHint: 'Optimize for AI/LLM processing: proper context passing, memory nodes for conversation history, tool-enabled agent architecture, and robust AI output parsing.', + compilerHint: 'Use AI Agent with memory, proper system/user message separation, output parsing', + }); + } + + // Add domain-specific strategy + if (intent.domain === 'data_pipeline') { + baseStrategies.push({ + name: 'Pipeline-Optimized', + plannerHint: 'Optimize for data pipeline: batch processing, deduplication, efficient array handling with SplitInBatches, and data validation at each stage.', + compilerHint: 'Use SplitInBatches for arrays, ItemLists for dedup, Filter for validation', + }); + } + + return baseStrategies; + } + + // ─── Candidate scoring ──────────────────────────────────────────────────── + private scoreCandidate( + validation: ValidationReport, + workflow: N8nWorkflow, + graph: WorkflowGraph, + ): number { + let score = validation.overallScore; + + // Bonus: has monitoring nodes + const hasMonitoring = graph.nodes.some((n) => n.layer === 'monitoring'); + if (hasMonitoring) score = Math.min(100, score + 5); + + // Bonus: all external nodes have retry + const externalNodes = workflow.nodes.filter((n) => !n.type.includes('trigger') && !n.type.includes('noOp')); + const allHaveRetry = externalNodes.every((n) => n.retryOnFail); + if (allHaveRetry && externalNodes.length > 0) score = Math.min(100, score + 5); + + // Bonus: has expressions (not static) + const paramsStr = JSON.stringify(workflow.nodes.map((n) => n.parameters)); + const expressionCount = (paramsStr.match(/\{\{/g) ?? []).length; + if (expressionCount >= 3) score = Math.min(100, score + 5); + + // Penalty: unknown nodes present + const unknownRejected = ((graph.metadata as any)?.unknownNodesRejected as string[]) ?? []; + if (unknownRejected.length > 0) score = Math.max(0, score - 20); + + // Penalty: active: true (should never happen but check) + if ((workflow.active as any) === true) score = 0; + + return Math.round(score); + } + + private buildSelectionReason(winner: SwarmCandidate, ranked: SwarmCandidate[]): string { + const others = ranked.slice(1); + const gaps = others.map((c) => `${c.id}(${c.qualityScore})`).join(', '); + return `Selected "${winner.planningStrategy}" strategy with score ${winner.qualityScore}/100. ` + + `Outperformed: ${gaps || 'no other candidates'}. ` + + `Validation: ${winner.validationReport.valid ? 'PASSED' : 'FAILED'}. ` + + `Issues: ${winner.validationReport.issues.length} errors, ${winner.validationReport.warnings.length} warnings.`; + } +} diff --git a/apps/simulator/src/services/webhookAutoBind.ts b/apps/simulator/src/services/webhookAutoBind.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ccfbdc09afed5d57f8243668f58da0e89aea854 --- /dev/null +++ b/apps/simulator/src/services/webhookAutoBind.ts @@ -0,0 +1,409 @@ +/** + * Webhook Auto-Bind System — NEW + * Automatically detects trigger type from intent and assigns correct trigger node + * Auto-generates webhook paths, auto-configures trigger settings + * User NEVER manually defines triggers + */ +import type { WorkflowIntent, WorkflowGraph } from '../types/workflow'; +import { getNodeDef } from '../knowledge/nodeRegistry'; + +export interface TriggerConfig { + nodeType: string; + displayName: string; + parameters: Record; + credentials?: Record; + webhookPath?: string; + notes: string; +} + +// ─── Trigger detection rules (ordered by specificity) ───────────────────────── +const TRIGGER_RULES: Array<{ + keywords: string[]; + integrations?: string[]; + domain?: string[]; + triggerType: string; + nodeType: string; + priority: number; +}> = [ + // Telegram + { + keywords: ['telegram', 'bot', 'message', 'chat'], + integrations: ['telegram'], + triggerType: 'telegram', + nodeType: 'n8n-nodes-base.telegramTrigger', + priority: 100, + }, + // Slack + { + keywords: ['slack', 'channel', 'workspace'], + integrations: ['slack'], + triggerType: 'slack', + nodeType: 'n8n-nodes-base.slackTrigger', + priority: 100, + }, + // GitHub + { + keywords: ['github', 'push', 'pull request', 'issue', 'commit', 'repository'], + integrations: ['github'], + triggerType: 'github', + nodeType: 'n8n-nodes-base.githubTrigger', + priority: 100, + }, + // Email (IMAP) + { + keywords: ['email', 'inbox', 'imap', 'mail received', 'new email', 'receive email'], + integrations: ['email', 'imap', 'gmail'], + triggerType: 'email', + nodeType: 'n8n-nodes-base.emailReadImap', + priority: 90, + }, + // Notion + { + keywords: ['notion', 'database page', 'notion page'], + integrations: ['notion'], + triggerType: 'notion', + nodeType: 'n8n-nodes-base.notionTrigger', + priority: 90, + }, + // Schedule / Cron + { + keywords: [ + 'every hour', 'every day', 'daily', 'hourly', 'weekly', 'monthly', 'schedule', + 'cron', 'at midnight', 'every morning', 'every night', 'periodic', 'interval', + 'every minute', 'every 5 minutes', 'nightly', 'scheduled', + ], + triggerType: 'schedule', + nodeType: 'n8n-nodes-base.scheduleTrigger', + priority: 80, + }, + // Webhook (general HTTP) + { + keywords: [ + 'webhook', 'api call', 'http request', 'post request', 'form submission', + 'when called', 'endpoint', 'receive data', 'api endpoint', 'rest api', + 'when a request', 'when triggered via', + ], + triggerType: 'webhook', + nodeType: 'n8n-nodes-base.webhook', + priority: 70, + }, + // Manual (fallback) + { + keywords: ['manually', 'manual trigger', 'on demand', 'test', 'demo'], + triggerType: 'manual', + nodeType: 'n8n-nodes-base.manualTrigger', + priority: 10, + }, +]; + +export class WebhookAutoBindSystem { + + /** + * Detect the appropriate trigger node type from intent and request text + */ + detectTrigger( + userRequest: string, + intent: WorkflowIntent, + ): { nodeType: string; triggerType: string; confidence: 'high' | 'medium' | 'low' } { + const requestLower = userRequest.toLowerCase(); + const integrationLower = intent.integrations.map((i) => i.toLowerCase()); + + let bestMatch: (typeof TRIGGER_RULES)[0] | undefined; + let bestScore = 0; + + for (const rule of TRIGGER_RULES) { + let score = 0; + + // Keyword matches in request + const keywordMatches = rule.keywords.filter((kw) => requestLower.includes(kw)); + score += keywordMatches.length * 10; + + // Integration matches + if (rule.integrations) { + const integrationMatches = rule.integrations.filter((i) => + integrationLower.some((intent_i) => intent_i.includes(i) || i.includes(intent_i)), + ); + score += integrationMatches.length * 20; + } + + // Exact trigger type hint from intent + if (intent.triggerType && intent.triggerType === rule.triggerType) { + score += 50; + } + + // Apply rule priority as tiebreaker + score += rule.priority * 0.1; + + if (score > bestScore) { + bestScore = score; + bestMatch = rule; + } + } + + // Default to webhook if nothing matched + const nodeType = bestMatch?.nodeType ?? 'n8n-nodes-base.webhook'; + const triggerType = bestMatch?.triggerType ?? 'webhook'; + const confidence: 'high' | 'medium' | 'low' = + bestScore >= 20 ? 'high' : bestScore >= 10 ? 'medium' : 'low'; + + return { nodeType, triggerType, confidence }; + } + + /** + * Generate complete trigger node configuration + */ + buildTriggerConfig( + nodeType: string, + userRequest: string, + intent: WorkflowIntent, + workflowName: string, + ): TriggerConfig { + switch (nodeType) { + case 'n8n-nodes-base.webhook': + return this.buildWebhookConfig(workflowName, intent); + + case 'n8n-nodes-base.telegramTrigger': + return this.buildTelegramTriggerConfig(); + + case 'n8n-nodes-base.scheduleTrigger': + return this.buildScheduleConfig(userRequest); + + case 'n8n-nodes-base.emailReadImap': + return this.buildEmailTriggerConfig(); + + case 'n8n-nodes-base.slackTrigger': + return this.buildSlackTriggerConfig(); + + case 'n8n-nodes-base.githubTrigger': + return this.buildGitHubTriggerConfig(); + + case 'n8n-nodes-base.notionTrigger': + return this.buildNotionTriggerConfig(); + + case 'n8n-nodes-base.manualTrigger': + return this.buildManualTriggerConfig(); + + default: + return this.buildWebhookConfig(workflowName, intent); + } + } + + /** + * Inject auto-configured trigger into a WorkflowGraph as the first node + */ + injectTriggerIntoGraph( + graph: WorkflowGraph, + triggerConfig: TriggerConfig, + triggerNodeType: string, + ): WorkflowGraph { + const def = getNodeDef(triggerNodeType); + const triggerId = 'trigger-auto-' + Date.now(); + + // Check if graph already has a trigger node from the detected type + const existingTrigger = graph.nodes.find((n) => n.layer === 'trigger'); + + if (existingTrigger) { + // Update existing trigger with proper config + const updatedNodes = graph.nodes.map((node) => { + if (node.layer !== 'trigger') return node; + return { + ...node, + n8nNodeType: triggerNodeType, + label: triggerConfig.displayName, + autoConfigured: true, + webhookPath: triggerConfig.webhookPath, + }; + }); + return { ...graph, nodes: updatedNodes }; + } + + // Insert new trigger at front + const newTriggerNode = { + id: triggerId, + label: triggerConfig.displayName, + n8nNodeType: triggerNodeType, + layer: 'trigger' as const, + description: triggerConfig.notes, + isCritical: true, + autoConfigured: true, + webhookPath: triggerConfig.webhookPath, + position: { x: 0, y: 300 }, + parameters: triggerConfig.parameters, + retryPolicy: undefined, + dataContract: { + inputs: [], + outputs: ['body', 'headers', 'query', 'params'], + }, + }; + + // Shift existing nodes to the right + const shiftedNodes = graph.nodes.map((node) => ({ + ...node, + position: { x: (node.position?.x ?? 0) + 220, y: node.position?.y ?? 300 }, + })); + + // Connect trigger to first non-trigger node + const firstNode = shiftedNodes[0]; + const newEdge = firstNode ? { + id: `edge-trigger-to-${firstNode.id}`, + sourceNodeId: triggerId, + targetNodeId: firstNode.id, + outputIndex: 0, + inputIndex: 0, + label: 'on receive', + } : undefined; + + return { + ...graph, + nodes: [newTriggerNode, ...shiftedNodes], + edges: newEdge ? [newEdge, ...graph.edges] : graph.edges, + }; + } + + // ─── Trigger config builders ────────────────────────────────────────────── + + private buildWebhookConfig(workflowName: string, intent: WorkflowIntent): TriggerConfig { + const slug = workflowName + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 40); + const path = `wfo/${slug}`; + + return { + nodeType: 'n8n-nodes-base.webhook', + displayName: 'Webhook Trigger', + parameters: { + httpMethod: intent.syncVsAsync === 'sync' ? 'POST' : 'POST', + path, + responseMode: intent.syncVsAsync === 'sync' ? 'lastNode' : 'onReceived', + responseData: 'allEntries', + options: {}, + }, + webhookPath: path, + notes: `Auto-configured webhook trigger. Path: /webhook/${path}. Method: POST. Response: ${intent.syncVsAsync === 'sync' ? 'synchronous (after processing)' : 'immediate acknowledgement'}.`, + }; + } + + private buildTelegramTriggerConfig(): TriggerConfig { + return { + nodeType: 'n8n-nodes-base.telegramTrigger', + displayName: 'Telegram Trigger', + parameters: { + updates: ['message', 'callback_query'], + }, + credentials: { + telegramApi: { id: '', name: 'Telegram Bot API' }, + }, + notes: 'Auto-configured Telegram trigger. Listens for messages and callback queries. Requires telegramApi credential.', + }; + } + + private buildScheduleConfig(userRequest: string): TriggerConfig { + // Detect schedule from request text + const req = userRequest.toLowerCase(); + let interval: Record[] = [{ field: 'hours', hoursInterval: 1 }]; + + if (req.includes('every minute')) interval = [{ field: 'minutes', minutesInterval: 1 }]; + else if (req.includes('every 5 minute')) interval = [{ field: 'minutes', minutesInterval: 5 }]; + else if (req.includes('every 15 minute')) interval = [{ field: 'minutes', minutesInterval: 15 }]; + else if (req.includes('every 30 minute')) interval = [{ field: 'minutes', minutesInterval: 30 }]; + else if (req.includes('every hour') || req.includes('hourly')) interval = [{ field: 'hours', hoursInterval: 1 }]; + else if (req.includes('every 6 hour')) interval = [{ field: 'hours', hoursInterval: 6 }]; + else if (req.includes('every 12 hour')) interval = [{ field: 'hours', hoursInterval: 12 }]; + else if (req.includes('daily') || req.includes('every day') || req.includes('nightly') || req.includes('midnight')) { + interval = [{ field: 'cronExpression', expression: '0 0 * * *' }]; + } + else if (req.includes('weekly') || req.includes('every week')) { + interval = [{ field: 'cronExpression', expression: '0 9 * * 1' }]; // Monday 9am + } + else if (req.includes('monthly') || req.includes('every month')) { + interval = [{ field: 'cronExpression', expression: '0 9 1 * *' }]; // 1st of month 9am + } + + return { + nodeType: 'n8n-nodes-base.scheduleTrigger', + displayName: 'Schedule Trigger', + parameters: { + rule: { interval }, + }, + notes: `Auto-configured schedule trigger. Interval detected from request: ${JSON.stringify(interval)}.`, + }; + } + + private buildEmailTriggerConfig(): TriggerConfig { + return { + nodeType: 'n8n-nodes-base.emailReadImap', + displayName: 'Email Trigger (IMAP)', + parameters: { + mailbox: 'INBOX', + action: 'read', + downloadAttachments: false, + format: 'simple', + options: {}, + }, + credentials: { + imap: { id: '', name: 'IMAP Account' }, + }, + notes: 'Auto-configured IMAP email trigger. Monitors INBOX for new emails. Requires IMAP credential.', + }; + } + + private buildSlackTriggerConfig(): TriggerConfig { + return { + nodeType: 'n8n-nodes-base.slackTrigger', + displayName: 'Slack Trigger', + parameters: { + trigger: 'any_message', + }, + credentials: { + slackOAuth2Api: { id: '', name: 'Slack OAuth2' }, + }, + notes: 'Auto-configured Slack trigger. Listens for messages in configured channels. Requires Slack OAuth2 credential.', + }; + } + + private buildGitHubTriggerConfig(): TriggerConfig { + return { + nodeType: 'n8n-nodes-base.githubTrigger', + displayName: 'GitHub Trigger', + parameters: { + owner: '', + repository: '', + events: ['push'], + }, + credentials: { + githubApi: { id: '', name: 'GitHub API' }, + }, + notes: 'Auto-configured GitHub trigger. Listens for push events. Set owner and repository in parameters.', + }; + } + + private buildNotionTriggerConfig(): TriggerConfig { + return { + nodeType: 'n8n-nodes-base.notionTrigger', + displayName: 'Notion Trigger', + parameters: { + databaseId: '', + event: 'page_added', + simple: true, + }, + credentials: { + notionApi: { id: '', name: 'Notion API' }, + }, + notes: 'Auto-configured Notion trigger. Monitors a database for new pages. Set databaseId in parameters.', + }; + } + + private buildManualTriggerConfig(): TriggerConfig { + return { + nodeType: 'n8n-nodes-base.manualTrigger', + displayName: 'Manual Trigger', + parameters: {}, + notes: 'Manual trigger — start this workflow by clicking "Execute Workflow" in n8n.', + }; + } +} + +// ─── Singleton export ───────────────────────────────────────────────────────── +export const webhookAutoBind = new WebhookAutoBindSystem(); diff --git a/apps/simulator/src/types/workflow.ts b/apps/simulator/src/types/workflow.ts new file mode 100644 index 0000000000000000000000000000000000000000..600c957d463c968e72c2b6edce15659ea6be088c --- /dev/null +++ b/apps/simulator/src/types/workflow.ts @@ -0,0 +1,50 @@ +/** + * Shared Types for Simulator Service + * Re-exports from worker types for consistency + */ +export type { + WorkflowIntent, + WorkflowArchitecturePlan, + WorkflowGraph, + GraphNode, + GraphEdge, + WorkflowMetadata, + WorkflowLayer, + N8nWorkflow, + N8nNode, + N8nConnections, + ValidationReport, + ValidationSection, + ValidationIssue, + SimulationReport, + ExecutionTraceStep, + FailurePoint, + ChaosTestResult, + CredentialAnalysis, + RequiredCredential, + AvailableCredential, + MissingCredential, + DeploymentReadiness, + WorkflowQualityScore, + GenerateWorkflowResponse, + RetryPolicy, + DataContract, + RiskLevel, + Risk, + RiskAnalysis, + DependencyAnalysis, + SubworkflowPlan, + TradeoffExplanation, + QualityTargets, + WorkflowDomain, + WorkflowLifecycleState, + ExecutionPlanStep, + WorkflowJob, + DeploymentResult, + ActivationResult, + ApprovalRequest, + AuditEvent, + SwarmResultSummary, + TriggerDetectionResult, + HealingResultSummary, +} from '@wfo/apps/worker/src/types/workflow'; diff --git a/apps/simulator/tsconfig.json b/apps/simulator/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..99f46b42df3a442e888e8a2465ace0543d226c4d --- /dev/null +++ b/apps/simulator/tsconfig.json @@ -0,0 +1,26 @@ +{ + // "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "node", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "../../", + + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "paths": { + "@wfo/apps/worker/*": ["../../apps/worker/*"], + "@wfo/core/*": ["../../core/*"], + "@wfo/integrations/*": ["../../integrations/*"], + "@wfo/validation/*": ["../../validation/*"], + "@wfo/observability/*": ["../../observability/*"], + "@wfo/knowledge/*": ["../../knowledge/*"], + "@wfo/config/*": ["../../config/*"] + } + }, + "include": ["src/**/*", "../../core/**/*", "../../integrations/**/*", "../../validation/**/*", "../../observability/**/*", "../../knowledge/**/*", "../../config/**/*", "../../apps/worker/src/types/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/worker/package.json b/apps/worker/package.json new file mode 100644 index 0000000000000000000000000000000000000000..85833ba372f07cccc0fbc21d7230c3bc5ecbe013 --- /dev/null +++ b/apps/worker/package.json @@ -0,0 +1,21 @@ +{ + "name": "@wfo/worker", + "version": "1.0.0", + "description": "Cloudflare Worker - API Gateway & Orchestrator (thin layer)", + "private": true, + "scripts": { + "dev": "wrangler dev", + "build": "wrangler deploy --dry-run", + "deploy": "wrangler deploy", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "hono": "^4.0.0", + "zod": "^3.22.0" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240208.0", + "wrangler": "^3.28.0", + "typescript": "^5.3.0" + } +} diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4054d1dbc18e0543d5adf18fc6df9c4df3920a2 --- /dev/null +++ b/apps/worker/src/index.ts @@ -0,0 +1,77 @@ +/** + * Cloudflare Worker - Main Entry Point + * Architecture: Thin API Gateway + Orchestrator + * Heavy logic is offloaded to external compute services (simulator, planner, compiler, validator) + * + * CF Workers Free Plan constraints: + * - 10ms CPU time limit per request + * - 128MB memory limit + * - No Cloudflare Queues + * - Limited KV operations + */ + +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { logger } from 'hono/logger'; +import { generateWorkflowRoute } from './routes/generate'; +import { validateWorkflowRoute } from './routes/validate'; +import { simulateWorkflowRoute } from './routes/simulate'; +import { deployWorkflowRoute } from './routes/deploy'; +import { approvalRoute } from './routes/approval'; +import { activateWorkflowRoute } from './routes/activate'; +import { reportsRoute } from './routes/reports'; +import { healthRoute } from './routes/health'; +import { authMiddleware } from './middleware/auth'; +import { rateLimitMiddleware } from './middleware/rateLimit'; +import { errorHandler } from './middleware/errorHandler'; +import type { Env } from './types/env'; + +const app = new Hono<{ Bindings: Env }>(); + +// ─── Global Middleware ──────────────────────────────────────────────────────── +app.use('*', cors({ + origin: ['https://your-frontend.pages.dev', 'http://localhost:3000'], + allowHeaders: ['Content-Type', 'Authorization', 'X-API-Key'], + allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + maxAge: 86400, +})); + +app.use('*', logger()); +app.use('*', errorHandler); + +// ─── Public Routes ──────────────────────────────────────────────────────────── +app.route('/health', healthRoute); + +// ─── Protected Routes (require API auth) ───────────────────────────────────── +app.use('/api/*', authMiddleware); +app.use('/api/*', rateLimitMiddleware); + +app.route('/api/workflows', generateWorkflowRoute); +app.route('/api/workflows', validateWorkflowRoute); +app.route('/api/workflows', simulateWorkflowRoute); +app.route('/api/workflows', deployWorkflowRoute); +app.route('/api/workflows', approvalRoute); +app.route('/api/workflows', activateWorkflowRoute); +app.route('/api/reports', reportsRoute); + +// ─── 404 Handler ───────────────────────────────────────────────────────────── +app.notFound((c) => { + return c.json({ + success: false, + error: 'Route not found', + available_routes: [ + 'POST /api/workflows/generate', + 'POST /api/workflows/validate', + 'POST /api/workflows/simulate', + 'POST /api/workflows/deploy', + 'POST /api/workflows/approve', + 'POST /api/workflows/activate', + 'GET /api/reports/workflow/:id', + 'GET /api/reports/validation/:id', + 'GET /api/reports/simulation/:id', + 'GET /health', + ], + }, 404); +}); + +export default app; diff --git a/apps/worker/src/middleware/auth.ts b/apps/worker/src/middleware/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..8fa9585abc98e72cdf8134e8ef82bb4c96ca2d7f --- /dev/null +++ b/apps/worker/src/middleware/auth.ts @@ -0,0 +1,32 @@ +/** + * Auth Middleware + * Validates X-API-Key header for protected routes + * Lightweight to stay within 10ms CF Workers CPU budget + */ +import type { MiddlewareHandler } from 'hono'; +import type { Env } from '../types/env'; + +export const authMiddleware: MiddlewareHandler<{ Bindings: Env }> = async (c, next) => { + const apiKey = c.req.header('X-API-Key') ?? c.req.header('Authorization')?.replace('Bearer ', ''); + + if (!apiKey) { + return c.json({ success: false, error: 'Missing API key. Provide X-API-Key header.' }, 401); + } + + // Constant-time comparison to prevent timing attacks + const expected = c.env.INTERNAL_API_SECRET; + if (!safeCompare(apiKey, expected)) { + return c.json({ success: false, error: 'Invalid API key.' }, 403); + } + + await next(); +}; + +function safeCompare(a: string, b: string): boolean { + if (a.length !== b.length) return false; + let result = 0; + for (let i = 0; i < a.length; i++) { + result |= (a.charCodeAt(i) ^ b.charCodeAt(i)); + } + return result === 0; +} diff --git a/apps/worker/src/middleware/errorHandler.ts b/apps/worker/src/middleware/errorHandler.ts new file mode 100644 index 0000000000000000000000000000000000000000..da923091eb8bafc8f6e7190ef355370c482334a6 --- /dev/null +++ b/apps/worker/src/middleware/errorHandler.ts @@ -0,0 +1,16 @@ +/** + * Error Handler Middleware + * Global error boundary for unhandled exceptions + */ +import type { MiddlewareHandler } from 'hono'; +import type { Env } from '../types/env'; + +export const errorHandler: MiddlewareHandler<{ Bindings: Env }> = async (c, next) => { + try { + await next(); + } catch (err) { + const message = err instanceof Error ? err.message : 'Internal server error'; + console.error('[WorkerError]', { path: c.req.path, error: message }); + return c.json({ success: false, error: message }, 500); + } +}; diff --git a/apps/worker/src/middleware/rateLimit.ts b/apps/worker/src/middleware/rateLimit.ts new file mode 100644 index 0000000000000000000000000000000000000000..40a2d9284432ba82b908bc307f1857fe1e936666 --- /dev/null +++ b/apps/worker/src/middleware/rateLimit.ts @@ -0,0 +1,50 @@ +/** + * Rate Limit Middleware + * Uses KV store for sliding window rate limiting + * Lightweight - primarily I/O bound (KV read/write), minimal CPU + */ +import type { MiddlewareHandler } from 'hono'; +import type { Env } from '../types/env'; + +const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute +const RATE_LIMIT_MAX_REQUESTS = 20; // 20 requests per minute per IP + +export const rateLimitMiddleware: MiddlewareHandler<{ Bindings: Env }> = async (c, next) => { + const ip = c.req.header('CF-Connecting-IP') ?? 'unknown'; + const key = `ratelimit:${ip}`; + + const raw = await c.env.WFO_CACHE.get(key, 'text'); + const now = Date.now(); + + let count = 1; + if (raw) { + const entry = JSON.parse(raw) as { count: number; windowStart: number }; + if (now - entry.windowStart < RATE_LIMIT_WINDOW_MS) { + count = entry.count + 1; + if (count > RATE_LIMIT_MAX_REQUESTS) { + return c.json({ + success: false, + error: 'Rate limit exceeded. Max 20 requests/minute.', + retryAfter: Math.ceil((entry.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000), + }, 429); + } + await c.env.WFO_CACHE.put(key, JSON.stringify({ count, windowStart: entry.windowStart }), { + expirationTtl: 120, + }); + } else { + // New window + await c.env.WFO_CACHE.put(key, JSON.stringify({ count: 1, windowStart: now }), { + expirationTtl: 120, + }); + } + } else { + await c.env.WFO_CACHE.put(key, JSON.stringify({ count: 1, windowStart: now }), { + expirationTtl: 120, + }); + } + + c.res.headers.set('X-RateLimit-Limit', String(RATE_LIMIT_MAX_REQUESTS)); + c.res.headers.set('X-RateLimit-Remaining', String(Math.max(0, RATE_LIMIT_MAX_REQUESTS - count))); + + await next(); +}; diff --git a/apps/worker/src/routes/activate.ts b/apps/worker/src/routes/activate.ts new file mode 100644 index 0000000000000000000000000000000000000000..d18d948da879b8a7ea1770011834e5cc06181493 --- /dev/null +++ b/apps/worker/src/routes/activate.ts @@ -0,0 +1,167 @@ +/** + * POST /api/workflows/activate + * Activation Engine - final safety gate + * Only activates workflows that have been: + * 1. Validated ✓ + * 2. Simulated ✓ + * 3. Deployed ✓ + * 4. Human Approved ✓ + * 5. Final credential check ✓ + * + * NEVER auto-activate. NEVER skip approval gate. + */ +import { Hono } from 'hono'; +import { z } from 'zod'; +import type { Env } from '../types/env'; +import type { WorkflowJob, ActivationResult } from '../types/workflow'; +import { createAuditEvent } from '../utils/audit'; + +export const activateWorkflowRoute = new Hono<{ Bindings: Env }>(); + +const ActivateRequestSchema = z.object({ + jobId: z.string().min(1), +}); + +activateWorkflowRoute.post('/activate', async (c) => { + const body = await c.req.json().catch(() => null); + if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400); + + const parsed = ActivateRequestSchema.safeParse(body); + if (!parsed.success) { + return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400); + } + + const { jobId } = parsed.data; + const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404); + + const job = JSON.parse(raw) as WorkflowJob; + + // ─── Final Safety Gates ────────────────────────────────────────────────── + if (job.state !== 'approved') { + const reason = job.state === 'rejected' + ? 'Workflow was rejected by reviewer.' + : job.state === 'awaiting_approval' + ? 'Workflow is awaiting human approval. Cannot activate yet.' + : `Workflow is not approved. Current state: ${job.state}`; + + return c.json({ success: false, error: reason, currentState: job.state }, 409); + } + + if (!job.deploymentResult?.n8nWorkflowId) { + return c.json({ success: false, error: 'Workflow not deployed to n8n. Run /deploy first.' }, 409); + } + + if (job.approvalRequest?.status !== 'approved') { + return c.json({ + success: false, + error: 'Human approval required before activation. No exceptions.', + }, 403); + } + + // ─── Final Credential Check ────────────────────────────────────────────── + const credResp = await fetch(`${c.env.N8N_BASE_URL}/api/v1/credentials`, { + headers: { 'X-N8N-API-KEY': c.env.N8N_API_KEY }, + signal: AbortSignal.timeout(8000), + }).catch(() => null); + + const credCheckPassed = credResp?.ok ?? false; + + // ─── Final Validation Check via simulator ──────────────────────────────── + const validationResp = await fetch(`${c.env.SIMULATOR_URL}/process/validate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Internal-Secret': c.env.INTERNAL_API_SECRET, + }, + body: JSON.stringify({ + jobId, + workflow: job.compiledWorkflow, + graph: job.graph, + final: true, + }), + signal: AbortSignal.timeout(10000), + }).catch(() => null); + + const finalValidationPassed = validationResp?.ok ?? false; + + if (!finalValidationPassed) { + const activationResult: ActivationResult = { + jobId, + n8nWorkflowId: job.deploymentResult.n8nWorkflowId, + activationStatus: 'failed', + finalValidationPassed: false, + finalCredentialCheckPassed: credCheckPassed, + activatedAt: new Date().toISOString(), + diagnostics: 'Final pre-activation validation failed. Workflow not activated.', + }; + + await updateJob(c.env.WFO_CACHE, jobId, job, { activationResult, state: 'deployed' }); + return c.json({ success: false, error: 'Pre-activation validation failed.', activationResult, jobId }, 500); + } + + // ─── Activate via n8n API ───────────────────────────────────────────────── + const activateResp = await fetch( + `${c.env.N8N_BASE_URL}/api/v1/workflows/${job.deploymentResult.n8nWorkflowId}/activate`, + { + method: 'POST', + headers: { 'X-N8N-API-KEY': c.env.N8N_API_KEY }, + signal: AbortSignal.timeout(10000), + }, + ).catch((err) => { throw new Error(`n8n activation failed: ${err instanceof Error ? err.message : 'unknown'}`); }); + + if (!activateResp.ok) { + const errText = await activateResp.text(); + const activationResult: ActivationResult = { + jobId, + n8nWorkflowId: job.deploymentResult.n8nWorkflowId, + activationStatus: 'failed', + finalValidationPassed, + finalCredentialCheckPassed: credCheckPassed, + activatedAt: new Date().toISOString(), + diagnostics: `n8n API activation error ${activateResp.status}: ${errText}`, + }; + + await updateJob(c.env.WFO_CACHE, jobId, job, { activationResult, state: 'deployed' }); + return c.json({ success: false, error: activationResult.diagnostics, activationResult, jobId }, 500); + } + + const activationResult: ActivationResult = { + jobId, + n8nWorkflowId: job.deploymentResult.n8nWorkflowId, + activationStatus: 'activated', + finalValidationPassed, + finalCredentialCheckPassed: credCheckPassed, + activatedAt: new Date().toISOString(), + }; + + await updateJob(c.env.WFO_CACHE, jobId, job, { activationResult, state: 'activated' }); + + return c.json({ + success: true, + jobId, + n8nWorkflowId: job.deploymentResult.n8nWorkflowId, + activationResult, + message: 'Workflow successfully activated in n8n.', + }); +}); + +async function updateJob( + kv: KVNamespace, + jobId: string, + job: WorkflowJob, + updates: { activationResult: ActivationResult; state: WorkflowJob['state'] }, +) { + const updated: WorkflowJob = { + ...job, + ...updates, + updatedAt: new Date().toISOString(), + auditLog: [ + ...job.auditLog, + createAuditEvent(jobId, `activation.${updates.activationResult.activationStatus}`, 'system', { + n8nWorkflowId: updates.activationResult.n8nWorkflowId, + }), + ], + }; + await kv.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 }); +} diff --git a/apps/worker/src/routes/approval.ts b/apps/worker/src/routes/approval.ts new file mode 100644 index 0000000000000000000000000000000000000000..bcdd41e630088fabedda79a12a89ecad0632f846 --- /dev/null +++ b/apps/worker/src/routes/approval.ts @@ -0,0 +1,169 @@ +/** + * POST /api/workflows/approve + * Human Approval System - core safety gate + * No workflow may activate without explicit human approval + * + * GET /api/workflows/approve/:jobId → get approval request details + * POST /api/workflows/approve → submit approval decision + */ +import { Hono } from 'hono'; +import { z } from 'zod'; +import type { Env } from '../types/env'; +import type { WorkflowJob, ApprovalRequest } from '../types/workflow'; +import { createAuditEvent } from '../utils/audit'; + +export const approvalRoute = new Hono<{ Bindings: Env }>(); + +const ApprovalRequestSchema = z.object({ + jobId: z.string().min(1), +}); + +const ApprovalDecisionSchema = z.object({ + jobId: z.string().min(1), + decision: z.enum(['approved', 'rejected']), + reviewerNote: z.string().max(1000).optional(), +}); + +// ─── GET approval request details ──────────────────────────────────────────── +approvalRoute.get('/approve/:jobId', async (c) => { + const jobId = c.req.param('jobId'); + const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404); + + const job = JSON.parse(raw) as WorkflowJob; + if (!job.approvalRequest) { + return c.json({ success: false, error: 'No approval request found for this job.' }, 404); + } + + return c.json({ + success: true, + jobId, + state: job.state, + approvalRequest: job.approvalRequest, + validationReport: job.validationReport, + simulationReport: job.simulationReport, + credentialAnalysis: job.credentialAnalysis, + architecturePlan: job.architecturePlan, + }); +}); + +// ─── POST request approval (after deploy) ──────────────────────────────────── +approvalRoute.post('/request-approval', async (c) => { + const body = await c.req.json().catch(() => null); + if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400); + + const parsed = ApprovalRequestSchema.safeParse(body); + if (!parsed.success) { + return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400); + } + + const { jobId } = parsed.data; + const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404); + + const job = JSON.parse(raw) as WorkflowJob; + + if (job.state !== 'deployed') { + return c.json({ + success: false, + error: `Job must be in 'deployed' state before requesting approval. Current state: ${job.state}`, + }, 409); + } + + const credentialStatus = job.credentialAnalysis?.allCredentialsPresent ? 'complete' : 'incomplete'; + + const approvalRequest: ApprovalRequest = { + jobId, + requestedAt: new Date().toISOString(), + expiresAt: new Date(Date.now() + 86400 * 1000).toISOString(), // 24h + architectureSummary: job.architecturePlan?.description ?? 'No architecture plan available', + riskSummary: job.architecturePlan?.riskAnalysis.overallRisk ?? 'unknown', + readinessScore: job.simulationReport?.readinessScore ?? 0, + validationScore: job.validationReport?.overallScore ?? 0, + simulationScore: job.simulationReport?.readinessScore ?? 0, + credentialStatus, + status: 'pending', + }; + + const updated: WorkflowJob = { + ...job, + state: 'awaiting_approval', + approvalRequest, + updatedAt: new Date().toISOString(), + auditLog: [ + ...job.auditLog, + createAuditEvent(jobId, 'approval.requested', 'system', { credentialStatus }), + ], + }; + + await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 }); + + return c.json({ + success: true, + jobId, + approvalRequest, + message: 'Approval request created. A human reviewer must approve before activation.', + reviewUrl: `/api/workflows/approve/${jobId}`, + }); +}); + +// ─── POST submit approval decision ─────────────────────────────────────────── +approvalRoute.post('/approve', async (c) => { + const body = await c.req.json().catch(() => null); + if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400); + + const parsed = ApprovalDecisionSchema.safeParse(body); + if (!parsed.success) { + return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400); + } + + const { jobId, decision, reviewerNote } = parsed.data; + const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404); + + const job = JSON.parse(raw) as WorkflowJob; + + if (job.state !== 'awaiting_approval') { + return c.json({ + success: false, + error: `Job is not awaiting approval. Current state: ${job.state}`, + }, 409); + } + + if (!job.approvalRequest) { + return c.json({ success: false, error: 'No pending approval request found.' }, 409); + } + + // Check expiry + if (new Date(job.approvalRequest.expiresAt) < new Date()) { + return c.json({ success: false, error: 'Approval request has expired. Re-run /request-approval.' }, 410); + } + + const updatedApproval: ApprovalRequest = { + ...job.approvalRequest, + status: decision, + reviewerNote, + respondedAt: new Date().toISOString(), + }; + + const newState = decision === 'approved' ? 'approved' : 'rejected'; + + const updated: WorkflowJob = { + ...job, + state: newState, + approvalRequest: updatedApproval, + updatedAt: new Date().toISOString(), + auditLog: [ + ...job.auditLog, + createAuditEvent(jobId, `approval.${decision}`, 'user', { reviewerNote }), + ], + }; + + await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 }); + + const message = decision === 'approved' + ? 'Workflow approved. You may now call /activate to activate it in n8n.' + : 'Workflow rejected. Deployment will not be activated.'; + + return c.json({ success: true, jobId, decision, newState, message }); +}); diff --git a/apps/worker/src/routes/deploy.ts b/apps/worker/src/routes/deploy.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b3b594792bca5423ae442e99e937d4ce121a5dc --- /dev/null +++ b/apps/worker/src/routes/deploy.ts @@ -0,0 +1,173 @@ +/** + * POST /api/workflows/deploy + * Deploy workflow to n8n via REST API + * Worker handles this directly (mostly external API calls, minimal CPU) + * + * Pipeline gate: Validate → Simulate → Deploy + * Deployment ≠ Activation (never auto-activate) + */ +import { Hono } from 'hono'; +import { z } from 'zod'; +import type { Env } from '../types/env'; +import type { WorkflowJob, DeploymentResult } from '../types/workflow'; +import { createAuditEvent } from '../utils/audit'; + +export const deployWorkflowRoute = new Hono<{ Bindings: Env }>(); + +const DeployRequestSchema = z.object({ + jobId: z.string().min(1), +}); + +deployWorkflowRoute.post('/deploy', async (c) => { + const body = await c.req.json().catch(() => null); + if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400); + + const parsed = DeployRequestSchema.safeParse(body); + if (!parsed.success) { + return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400); + } + + const { jobId } = parsed.data; + const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404); + + const job = JSON.parse(raw) as WorkflowJob; + + // ─── Pipeline Gates ────────────────────────────────────────────────────── + if (!job.compiledWorkflow) { + return c.json({ success: false, error: 'Workflow not compiled. Run /generate first.' }, 409); + } + if (!job.validationReport?.valid) { + return c.json({ + success: false, + error: 'Workflow failed validation. Fix issues before deploying.', + validationReport: job.validationReport, + }, 409); + } + if (!job.simulationReport?.passed) { + return c.json({ + success: false, + error: 'Workflow failed dry-run simulation. Fix issues before deploying.', + simulationReport: job.simulationReport, + }, 409); + } + + // ─── Credential check via n8n API ──────────────────────────────────────── + const credResp = await fetch(`${c.env.N8N_BASE_URL}/api/v1/credentials`, { + headers: { 'X-N8N-API-KEY': c.env.N8N_API_KEY }, + signal: AbortSignal.timeout(8000), + }).catch(() => null); + + let availableCredentials: Array<{ id: string; name: string; type: string }> = []; + if (credResp?.ok) { + const credData = await credResp.json() as { data: typeof availableCredentials }; + availableCredentials = credData.data ?? []; + } + + // ─── Deploy to n8n (create or update) ──────────────────────────────────── + const workflowPayload = { + ...job.compiledWorkflow, + active: false, // NEVER activate on deploy + }; + + let n8nResponse: Response; + let n8nWorkflowId = job.deploymentResult?.n8nWorkflowId; + + try { + if (n8nWorkflowId) { + // Update existing + n8nResponse = await fetch(`${c.env.N8N_BASE_URL}/api/v1/workflows/${n8nWorkflowId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-N8N-API-KEY': c.env.N8N_API_KEY, + }, + body: JSON.stringify(workflowPayload), + signal: AbortSignal.timeout(10000), + }); + } else { + // Create new + n8nResponse = await fetch(`${c.env.N8N_BASE_URL}/api/v1/workflows`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-N8N-API-KEY': c.env.N8N_API_KEY, + }, + body: JSON.stringify(workflowPayload), + signal: AbortSignal.timeout(10000), + }); + } + } catch (err) { + const msg = err instanceof Error ? err.message : 'n8n API unreachable'; + return c.json({ success: false, error: `Deployment failed: ${msg}`, jobId }, 502); + } + + if (!n8nResponse.ok) { + const errText = await n8nResponse.text(); + const deploymentResult: DeploymentResult = { + jobId, + status: 'failed', + deployedAt: new Date().toISOString(), + errorMessage: `n8n API ${n8nResponse.status}: ${errText}`, + }; + + await updateJobInKV(c.env.WFO_CACHE, jobId, job, { + state: 'simulated', // revert to pre-deploy state + deploymentResult, + event: 'deployment.failed', + }); + + return c.json({ success: false, error: deploymentResult.errorMessage, deploymentResult, jobId }, 500); + } + + const n8nData = await n8nResponse.json() as { id: string }; + n8nWorkflowId = n8nData.id; + + const deploymentResult: DeploymentResult = { + jobId, + n8nWorkflowId, + status: 'success', + deployedAt: new Date().toISOString(), + }; + + await updateJobInKV(c.env.WFO_CACHE, jobId, job, { + state: 'deployed', + deploymentResult, + event: 'deployment.success', + credentialAnalysis: { + jobId, + allCredentialsPresent: availableCredentials.length > 0, + required: [], + available: availableCredentials, + missing: [], + analysedAt: new Date().toISOString(), + }, + }); + + return c.json({ + success: true, + jobId, + n8nWorkflowId, + deploymentResult, + message: 'Workflow deployed successfully. It is NOT yet activated. Submit /approve to request activation.', + }); +}); + +async function updateJobInKV( + kv: KVNamespace, + jobId: string, + job: WorkflowJob, + updates: Partial & { event: string }, +) { + const { event, ...rest } = updates; + const updated: WorkflowJob = { + ...job, + ...rest, + updatedAt: new Date().toISOString(), + auditLog: [ + ...job.auditLog, + createAuditEvent(jobId, event, 'system', rest as Record), + ], + }; + await kv.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 }); +} diff --git a/apps/worker/src/routes/generate.ts b/apps/worker/src/routes/generate.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e44e1e054911379704fbec257d2a652303a48ae --- /dev/null +++ b/apps/worker/src/routes/generate.ts @@ -0,0 +1,149 @@ +/** + * POST /api/workflows/generate — UPGRADED + * CF Worker role: Thin orchestrator + API gateway + * Passes LLM config to simulator (provider-agnostic, no direct OpenAI dep) + */ +import { Hono } from 'hono'; +import { z } from 'zod'; +import type { Env } from '../types/env'; +import type { WorkflowJob, GenerateWorkflowResponse } from '../types/workflow'; +import { createAuditEvent } from '../utils/audit'; +import { generateJobId } from '../utils/ids'; + +export const generateWorkflowRoute = new Hono<{ Bindings: Env }>(); + +const GenerateRequestSchema = z.object({ + request: z.string().min(10, 'Request must be at least 10 characters').max(2000), + options: z.object({ + skipSimulation: z.boolean().optional().default(false), + requireApproval: z.boolean().optional().default(true), + swarmSize: z.number().min(1).max(5).optional().default(3), + skipSwarm: z.boolean().optional().default(false), + skipSelfHealing: z.boolean().optional().default(false), + }).optional().default({}), +}); + +generateWorkflowRoute.post('/generate', async (c) => { + // ─── Parse & validate request ───────────────────────────────────────────── + const body = await c.req.json().catch(() => null); + if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400); + + const parsed = GenerateRequestSchema.safeParse(body); + if (!parsed.success) { + return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400); + } + + const { request, options } = parsed.data; + const jobId = generateJobId(); + + // ─── Create initial job in KV ───────────────────────────────────────────── + const job: WorkflowJob = { + id: jobId, + userRequest: request, + state: 'draft', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + auditLog: [createAuditEvent(jobId, 'job.created', 'user', { request })], + }; + + await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(job), { + expirationTtl: 86400 * 7, + }); + + // ─── Build LLM config for simulator (provider-agnostic) ────────────────── + // Resolve API key: prefer new LLM_API_KEY, fallback to legacy OPENAI_API_KEY + const llmApiKey = c.env.LLM_API_KEY || c.env.OPENAI_API_KEY || ''; + const llmModel = c.env.LLM_MODEL || 'gpt-4o'; + const llmGatewayUrl = c.env.LLM_GATEWAY_URL || 'https://api.openai.com/v1'; + const llmProvider = c.env.LLM_PROVIDER || 'openai'; + + if (!llmApiKey) { + return c.json({ + success: false, + error: 'LLM API key not configured. Set LLM_API_KEY secret in wrangler.', + jobId, + }, 500); + } + + // ─── Offload to simulator service (heavy CPU/LLM work) ─────────────────── + const simulatorPayload = { + jobId, + request, + options, + // Provider-agnostic LLM config + llmApiKey, + llmModel, + llmGatewayUrl, + llmProvider, + // Legacy field for backward compat with older simulator versions + openaiApiKey: llmApiKey, + // n8n config + n8nBaseUrl: c.env.N8N_BASE_URL, + n8nApiKey: c.env.N8N_API_KEY, + }; + + let simulatorResponse: Response; + try { + simulatorResponse = await fetch(`${c.env.SIMULATOR_URL}/process/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Internal-Secret': c.env.INTERNAL_API_SECRET, + }, + body: JSON.stringify(simulatorPayload), + signal: AbortSignal.timeout(55000), // 55s — allow swarm + healing time + }); + } catch (err) { + const errMsg = err instanceof Error ? err.message : 'Simulator unreachable'; + await updateJobState(c.env.WFO_CACHE, jobId, 'draft', errMsg); + return c.json({ success: false, error: `Simulator service error: ${errMsg}`, jobId }, 502); + } + + if (!simulatorResponse.ok) { + const errText = await simulatorResponse.text(); + return c.json({ success: false, error: `Simulator returned ${simulatorResponse.status}: ${errText}`, jobId }, 502); + } + + const result = await simulatorResponse.json() as GenerateWorkflowResponse; + + // ─── Store full result in KV ────────────────────────────────────────────── + const updatedJob: WorkflowJob = { + ...job, + state: result.state, + updatedAt: new Date().toISOString(), + architecturePlan: result.architecturePlan, + graph: result.graph, + compiledWorkflow: result.compiledWorkflow, + validationReport: result.validationReport, + simulationReport: result.simulationReport, + credentialAnalysis: result.credentialAnalysis, + qualityScore: result.qualityScore, + auditLog: [ + ...job.auditLog, + createAuditEvent(jobId, 'job.generated', 'ai', { + state: result.state, + qualityScore: result.qualityScore?.overallScore, + swarmSize: result.swarmResult?.swarmSize, + triggerType: result.triggerDetection?.triggerType, + }), + ], + }; + + await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updatedJob), { + expirationTtl: 86400 * 7, + }); + + return c.json({ success: true, jobId, ...result }, 200); +}); + +async function updateJobState(kv: KVNamespace, jobId: string, state: string, error?: string) { + const raw = await kv.get(`job:${jobId}`, 'text'); + if (!raw) return; + const job = JSON.parse(raw) as WorkflowJob; + await kv.put(`job:${jobId}`, JSON.stringify({ + ...job, + state, + updatedAt: new Date().toISOString(), + error, + }), { expirationTtl: 86400 * 7 }); +} diff --git a/apps/worker/src/routes/health.ts b/apps/worker/src/routes/health.ts new file mode 100644 index 0000000000000000000000000000000000000000..51049ad4f7d599bfea449846e416692041d7d143 --- /dev/null +++ b/apps/worker/src/routes/health.ts @@ -0,0 +1,51 @@ +/** + * GET /health — UPGRADED + * Public health check endpoint with system capabilities info + */ +import { Hono } from 'hono'; +import type { Env } from '../types/env'; + +export const healthRoute = new Hono<{ Bindings: Env }>(); + +healthRoute.get('/', async (c) => { + return c.json({ + status: 'ok', + service: 'workflow-factor-os', + version: '2.0.0', + environment: c.env.ENVIRONMENT, + timestamp: new Date().toISOString(), + capabilities: { + llmGateway: 'provider-agnostic (openai | anthropic | openai-compatible | custom)', + llmProvider: c.env.LLM_PROVIDER ?? 'openai', + llmModel: c.env.LLM_MODEL ?? 'gpt-4o', + swarmOrchestration: 'parallel multi-agent design (up to 5 candidates)', + selfHealing: 'automatic validation fix + AI-assisted healing', + webhookAutoBind: 'auto-trigger detection and configuration', + memorySystem: 'in-process pattern learning and anti-pattern prevention', + nodeRegistry: 'strict — only verified n8n node types allowed', + safetyGates: 'validate → simulate → deploy → approve → activate', + }, + endpoints: { + generate: 'POST /api/workflows/generate', + validate: 'POST /api/workflows/validate', + simulate: 'POST /api/workflows/simulate', + deploy: 'POST /api/workflows/deploy', + requestApproval: 'POST /api/workflows/request-approval', + approve: 'POST /api/workflows/approve', + activate: 'POST /api/workflows/activate', + reports: { + workflow: 'GET /api/reports/workflow/:id', + validation: 'GET /api/reports/validation/:id', + simulation: 'GET /api/reports/simulation/:id', + credential: 'GET /api/reports/credential/:id', + audit: 'GET /api/reports/audit/:id', + }, + }, + safetyPolicy: { + autoActivation: 'DISABLED — human approval always required', + partialWorkflows: 'REJECTED — never deploy partially valid workflows', + unknownNodes: 'REJECTED — only registered n8n node types allowed', + activeOnDeploy: 'FORCED FALSE — active: false enforced at all stages', + }, + }); +}); diff --git a/apps/worker/src/routes/reports.ts b/apps/worker/src/routes/reports.ts new file mode 100644 index 0000000000000000000000000000000000000000..29c137f7191643a312691d1dc692117ca063108e --- /dev/null +++ b/apps/worker/src/routes/reports.ts @@ -0,0 +1,73 @@ +/** + * GET /api/reports/workflow/:id + * GET /api/reports/validation/:id + * GET /api/reports/simulation/:id + * GET /api/reports/credential/:id + * GET /api/reports/audit/:id + */ +import { Hono } from 'hono'; +import type { Env } from '../types/env'; +import type { WorkflowJob } from '../types/workflow'; + +export const reportsRoute = new Hono<{ Bindings: Env }>(); + +// Full workflow job report +reportsRoute.get('/workflow/:id', async (c) => { + const id = c.req.param('id'); + const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404); + const job = JSON.parse(raw) as WorkflowJob; + return c.json({ success: true, job }); +}); + +// Validation report only +reportsRoute.get('/validation/:id', async (c) => { + const id = c.req.param('id'); + const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404); + const job = JSON.parse(raw) as WorkflowJob; + if (!job.validationReport) { + return c.json({ success: false, error: 'No validation report available yet.' }, 404); + } + return c.json({ success: true, jobId: id, validationReport: job.validationReport }); +}); + +// Simulation report only +reportsRoute.get('/simulation/:id', async (c) => { + const id = c.req.param('id'); + const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404); + const job = JSON.parse(raw) as WorkflowJob; + if (!job.simulationReport) { + return c.json({ success: false, error: 'No simulation report available yet.' }, 404); + } + return c.json({ success: true, jobId: id, simulationReport: job.simulationReport }); +}); + +// Credential analysis +reportsRoute.get('/credential/:id', async (c) => { + const id = c.req.param('id'); + const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404); + const job = JSON.parse(raw) as WorkflowJob; + if (!job.credentialAnalysis) { + return c.json({ success: false, error: 'No credential analysis available yet.' }, 404); + } + return c.json({ success: true, jobId: id, credentialAnalysis: job.credentialAnalysis }); +}); + +// Audit trail +reportsRoute.get('/audit/:id', async (c) => { + const id = c.req.param('id'); + const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404); + const job = JSON.parse(raw) as WorkflowJob; + return c.json({ + success: true, + jobId: id, + state: job.state, + auditLog: job.auditLog, + createdAt: job.createdAt, + updatedAt: job.updatedAt, + }); +}); diff --git a/apps/worker/src/routes/simulate.ts b/apps/worker/src/routes/simulate.ts new file mode 100644 index 0000000000000000000000000000000000000000..74706dede74dd13d44f48d8e3e4cc5a82a00e95d --- /dev/null +++ b/apps/worker/src/routes/simulate.ts @@ -0,0 +1,89 @@ +/** + * POST /api/workflows/simulate + * Dry-run simulation - offloaded entirely to external compute service + * CF Worker just proxies the request and stores results in KV + */ +import { Hono } from 'hono'; +import { z } from 'zod'; +import type { Env } from '../types/env'; +import type { WorkflowJob } from '../types/workflow'; +import { createAuditEvent } from '../utils/audit'; + +export const simulateWorkflowRoute = new Hono<{ Bindings: Env }>(); + +const SimulateRequestSchema = z.object({ + jobId: z.string().min(1), + mockData: z.record(z.unknown()).optional(), +}); + +simulateWorkflowRoute.post('/simulate', async (c) => { + const body = await c.req.json().catch(() => null); + if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400); + + const parsed = SimulateRequestSchema.safeParse(body); + if (!parsed.success) { + return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400); + } + + const { jobId, mockData } = parsed.data; + const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404); + + const job = JSON.parse(raw) as WorkflowJob; + + if (!job.compiledWorkflow || !job.graph) { + return c.json({ + success: false, + error: 'Workflow not compiled or validated. Run /generate and /validate first.', + }, 409); + } + + if (job.validationReport && !job.validationReport.valid) { + return c.json({ + success: false, + error: 'Cannot simulate an invalid workflow. Fix validation issues first.', + validationReport: job.validationReport, + }, 409); + } + + // Offload simulation to external compute (CPU-intensive) + const resp = await fetch(`${c.env.SIMULATOR_URL}/process/simulate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Internal-Secret': c.env.INTERNAL_API_SECRET, + }, + body: JSON.stringify({ + jobId, + workflow: job.compiledWorkflow, + graph: job.graph, + architecturePlan: job.architecturePlan, + mockData: mockData ?? {}, + }), + signal: AbortSignal.timeout(25000), + }).catch((e) => { throw new Error(`Simulator error: ${e instanceof Error ? e.message : 'unknown'}`); }); + + if (!resp.ok) { + return c.json({ success: false, error: `Simulator returned ${resp.status}` }, 502); + } + + const result = await resp.json() as { simulationReport: WorkflowJob['simulationReport'] }; + + const updated: WorkflowJob = { + ...job, + state: result.simulationReport?.passed ? 'simulated' : 'validated', + simulationReport: result.simulationReport, + updatedAt: new Date().toISOString(), + auditLog: [ + ...job.auditLog, + createAuditEvent(jobId, 'simulation.completed', 'system', { + passed: result.simulationReport?.passed, + readinessScore: result.simulationReport?.readinessScore, + }), + ], + }; + + await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 }); + + return c.json({ success: true, jobId, simulationReport: result.simulationReport }); +}); diff --git a/apps/worker/src/routes/validate.ts b/apps/worker/src/routes/validate.ts new file mode 100644 index 0000000000000000000000000000000000000000..3522ec70939ea0814c0aea02e16ab3cbbf67732c --- /dev/null +++ b/apps/worker/src/routes/validate.ts @@ -0,0 +1,77 @@ +/** + * POST /api/workflows/validate + * Triggers validation on an already-generated workflow job + * Offloads to external simulator service + */ +import { Hono } from 'hono'; +import { z } from 'zod'; +import type { Env } from '../types/env'; +import type { WorkflowJob } from '../types/workflow'; +import { createAuditEvent } from '../utils/audit'; + +export const validateWorkflowRoute = new Hono<{ Bindings: Env }>(); + +const ValidateRequestSchema = z.object({ + jobId: z.string().min(1), +}); + +validateWorkflowRoute.post('/validate', async (c) => { + const body = await c.req.json().catch(() => null); + if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400); + + const parsed = ValidateRequestSchema.safeParse(body); + if (!parsed.success) { + return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400); + } + + const { jobId } = parsed.data; + const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text'); + if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404); + + const job = JSON.parse(raw) as WorkflowJob; + if (!job.compiledWorkflow) { + return c.json({ success: false, error: 'Workflow not yet compiled. Run /generate first.' }, 409); + } + + // Offload validation to external compute service + const resp = await fetch(`${c.env.SIMULATOR_URL}/process/validate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Internal-Secret': c.env.INTERNAL_API_SECRET, + }, + body: JSON.stringify({ + jobId, + workflow: job.compiledWorkflow, + graph: job.graph, + n8nBaseUrl: c.env.N8N_BASE_URL, + n8nApiKey: c.env.N8N_API_KEY, + }), + signal: AbortSignal.timeout(15000), + }).catch((e) => { throw new Error(`Simulator error: ${e instanceof Error ? e.message : 'unknown'}`); }); + + if (!resp.ok) { + return c.json({ success: false, error: `Simulator returned ${resp.status}` }, 502); + } + + const result = await resp.json() as { validationReport: WorkflowJob['validationReport'] }; + + // Update job in KV + const updated: WorkflowJob = { + ...job, + state: result.validationReport?.valid ? 'validated' : 'generated', + validationReport: result.validationReport, + updatedAt: new Date().toISOString(), + auditLog: [ + ...job.auditLog, + createAuditEvent(jobId, 'validation.completed', 'system', { + valid: result.validationReport?.valid, + score: result.validationReport?.overallScore, + }), + ], + }; + + await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 }); + + return c.json({ success: true, jobId, validationReport: result.validationReport }); +}); diff --git a/apps/worker/src/types/env.ts b/apps/worker/src/types/env.ts new file mode 100644 index 0000000000000000000000000000000000000000..e267e893f3f8e9f1699e3db404210ea39047ec5c --- /dev/null +++ b/apps/worker/src/types/env.ts @@ -0,0 +1,33 @@ +/** + * Cloudflare Worker Environment Bindings — UPGRADED + * Provider-agnostic LLM config (no direct OpenAI dependency) + * Supports any OpenAI-compatible endpoint, Anthropic, or custom gateway + */ +type KVNamespace = any; +export interface Env { + // ─── KV Namespaces ────────────────────────────────────────────────────────── + WFO_CACHE: KVNamespace; // workflow jobs, reports, approval states + + // ─── LLM Gateway Secrets (provider-agnostic) ───────────────────────────── + LLM_API_KEY: string; // provider API key (OpenAI, Anthropic, or custom) + LLM_GATEWAY_URL: string; // e.g. https://api.openai.com/v1 or custom endpoint + LLM_MODEL: string; // e.g. gpt-4o, claude-3-5-sonnet-20241022, etc. + LLM_PROVIDER: string; // openai | anthropic | openai-compatible | custom + + // ─── Legacy fallback (for backward compatibility) ───────────────────────── + OPENAI_API_KEY: string; // deprecated — use LLM_API_KEY instead + + // ─── Fallback LLM provider (optional) ──────────────────────────────────── + LLM_FALLBACK_URL: string; // fallback gateway URL (optional) + LLM_FALLBACK_KEY: string; // fallback API key (optional) + LLM_FALLBACK_MODEL: string; // fallback model (optional) + + // ─── n8n Instance ──────────────────────────────────────────────────────── + N8N_API_KEY: string; // n8n REST API key + INTERNAL_API_SECRET: string; // shared secret between Worker ↔ Simulator service + + // ─── Env Vars (wrangler.toml [vars]) ───────────────────────────────────── + ENVIRONMENT: 'development' | 'production'; + SIMULATOR_URL: string; // external compute service URL (Express/Node.js) + N8N_BASE_URL: string; // n8n instance base URL +} diff --git a/apps/worker/src/types/workflow.ts b/apps/worker/src/types/workflow.ts new file mode 100644 index 0000000000000000000000000000000000000000..d90492a212d7f18f801878984c73d79adec312fd --- /dev/null +++ b/apps/worker/src/types/workflow.ts @@ -0,0 +1,499 @@ +/** + * Shared Workflow Types — UPGRADED + * Complete type definitions for all workflow stages + * Supports Swarm, Self-Healing, Memory, WebhookAutoBind outputs + */ + +// ─── Domain / Meta types ────────────────────────────────────────────────────── + +export type WorkflowDomain = + | 'sales_automation' + | 'ai_workflow' + | 'data_pipeline' + | 'webhook_system' + | 'notification' + | 'crm_integration' + | 'devops' + | 'ecommerce' + | 'general'; + +export type RiskLevel = 'low' | 'medium' | 'high' | 'critical'; + +export type WorkflowLifecycleState = + | 'draft' + | 'generated' + | 'validated' + | 'simulated' + | 'deployed' + | 'awaiting_approval' + | 'approved' + | 'rejected' + | 'activated' + | 'failed'; + +export type WorkflowLayer = + | 'trigger' + | 'validation' + | 'transform' + | 'ai' + | 'integration' + | 'control' + | 'database' + | 'utility' + | 'monitoring' + | 'error_handling'; + +// ─── Intent ────────────────────────────────────────────────────────────────── + +export interface WorkflowIntent { + workflowType: string; + requiresAI: boolean; + integrations: string[]; + riskLevel: RiskLevel; + requiresHumanApproval: boolean; + syncVsAsync: 'sync' | 'async' | 'hybrid'; + scalingRequirements: 'low' | 'medium' | 'high'; + identifiedRisks: string[]; + estimatedComplexity: 'simple' | 'moderate' | 'complex'; + domain: WorkflowDomain; + triggerType?: string; // Auto-detected trigger type + triggerHint?: string; // Why this trigger was detected +} + +// ─── Architecture Plan ──────────────────────────────────────────────────────── + +export interface ExecutionPlanStep { + step: number; + layer: string; + nodes: string[]; + description: string; +} + +export interface Risk { + type: string; + probability: RiskLevel; + impact: RiskLevel; + description: string; +} + +export interface RiskAnalysis { + overallRisk: RiskLevel; + risks: Risk[]; + mitigations: string[]; +} + +export interface TradeoffExplanation { + option: string; + pros: string[]; + cons: string[]; + recommendation: string; +} + +export interface QualityTargets { + reliability: number; + performance: string; + observability: string; +} + +export interface SubworkflowPlan { + name: string; + trigger: string; + purpose: string; +} + +export interface DependencyAnalysis { + external: string[]; + credentials: string[]; + requiredPermissions: string[]; +} + +export interface WorkflowArchitecturePlan { + name: string; + description: string; + triggerNode: string; + triggerDescription: string; + layers: ExecutionPlanStep[]; + nodeList: Array<{ + displayName: string; + n8nNodeType: string; + layer: string; + purpose: string; + isCritical: boolean; + requiresCredential: string | null; + keyParameters?: Record; + }>; + dataFlow: string[]; + riskAnalysis: RiskAnalysis; + estimatedNodes: number; + complexity: 'simple' | 'moderate' | 'complex'; + qualityTargets?: QualityTargets; + tradeoffs?: TradeoffExplanation[]; + subworkflows?: SubworkflowPlan[]; + dependencyAnalysis?: DependencyAnalysis; +} + +// ─── Graph (IR) ─────────────────────────────────────────────────────────────── + +export interface RetryPolicy { + maxRetries: number; + retryDelayMs: number; + retryOn: string[]; +} + +export interface DataContract { + inputs: string[]; + outputs: string[]; +} + +export interface GraphNode { + id: string; + label: string; + n8nNodeType: string; + layer: WorkflowLayer; + description: string; + isCritical: boolean; + position: { x: number; y: number }; + retryPolicy?: RetryPolicy; + fallbackNodeId?: string; + dataContract?: DataContract; + keyParameters?: Record; + autoConfigured?: boolean; + webhookPath?: string; +} + +export interface GraphEdge { + id: string; + sourceNodeId: string; + targetNodeId: string; + outputIndex: number; + inputIndex: number; + label?: string; +} + +export interface WorkflowMetadata { + domain: string; + version: string; + createdAt: string; + estimatedNodes?: number; + estimatedDuration?: string; + riskLevel?: RiskLevel; + unknownNodesRejected?: string[]; +} + +export interface WorkflowGraph { + workflowId: string; + name: string; + nodes: GraphNode[]; + edges: GraphEdge[]; + metadata: WorkflowMetadata; +} + +// ─── n8n Workflow JSON ──────────────────────────────────────────────────────── + +export interface N8nNode { + id: string; + name: string; + type: string; + typeVersion: number; + position: [number, number]; + parameters: Record; + credentials?: Record; + onError?: string; + retryOnFail?: boolean; + maxTries?: number; + waitBetweenTries?: number; + notes?: string; +} + +export interface N8nConnections { + [nodeName: string]: { + main: Array>; + }; +} + +export interface N8nWorkflow { + name: string; + active: false; // ALWAYS false — never auto-activate + nodes: N8nNode[]; + connections: N8nConnections; + settings?: { + executionOrder?: string; + saveManualExecutions?: boolean; + timezone?: string; + errorWorkflow?: string; + callerPolicy?: string; + }; + tags?: string[]; + meta?: Record; +} + +// ─── Validation ─────────────────────────────────────────────────────────────── + +export interface ValidationIssue { + id: string; + severity: 'error' | 'warning' | 'info'; + category: string; + nodeId?: string; + message: string; + suggestion: string; +} + +export interface ValidationSection { + passed: boolean; + score: number; + issues: ValidationIssue[]; +} + +export interface ValidationReport { + jobId: string; + valid: boolean; + overallScore: number; + schemaValidation: ValidationSection; + credentialValidation: ValidationSection; + graphValidation: ValidationSection; + expressionValidation: ValidationSection; + reliabilityValidation: ValidationSection; + nodeRegistryValidation?: ValidationSection; // NEW + dataFlowValidation?: ValidationSection; // NEW + issues: ValidationIssue[]; // errors only + warnings: ValidationIssue[]; + validatedAt: string; +} + +// ─── Simulation ─────────────────────────────────────────────────────────────── + +export interface ExecutionTraceStep { + stepNumber: number; + nodeId: string; + nodeName: string; + nodeType: string; + status: 'success' | 'warning' | 'failure'; + inputData: Record; + outputData: Record; + executionTimeMs: number; + issues?: string[]; + notes?: string; +} + +export interface FailurePoint { + nodeId: string; + nodeName: string; + failureType: string; + probability: RiskLevel; + description: string; + mitigation: string; +} + +export interface ChaosTestResult { + scenario: 'timeout' | 'null_data' | 'malformed_input' | 'expired_credentials' | 'rate_limit' | 'network_error'; + passed: boolean; + affectedNode: string; + description: string; + resilient: boolean; + fallbackActivated: boolean; +} + +export interface SimulationReport { + jobId: string; + passed: boolean; + readinessScore: number; + executionTrace: ExecutionTraceStep[]; + riskAnalysis: RiskAnalysis; + predictedFailurePoints: FailurePoint[]; + chaosTestResults: ChaosTestResult[]; + deploymentBlockers?: string[]; // NEW + recommendations?: string[]; // NEW + mockDataUsed: Record; + simulatedAt: string; +} + +// ─── Credentials ───────────────────────────────────────────────────────────── + +export interface RequiredCredential { + nodeId: string; + nodeName: string; + credentialType: string; + credentialDisplayName?: string; + required: boolean; +} + +export interface AvailableCredential { + id: string; + name: string; + type: string; +} + +export interface MissingCredential { + credentialType: string; + requiredByNodes: string[]; + setupInstructions: string; +} + +export interface CredentialAnalysis { + jobId: string; + allCredentialsPresent: boolean; + n8nReachable?: boolean; // NEW + required: RequiredCredential[]; + available: AvailableCredential[]; + missing: MissingCredential[]; + analysedAt: string; +} + +// ─── Quality Scoring ───────────────────────────────────────────────────────── + +export interface DeploymentReadiness { + deploymentReady: boolean; + confidenceScore: number; + riskLevel: RiskLevel; + blockingIssues: string[]; + recommendations: string[]; +} + +export interface WorkflowQualityScore { + overallScore: number; + reliabilityScore: number; + maintainabilityScore: number; + observabilityScore: number; + retryCoverage: number; + expressionSafety: number; + credentialReadiness: number; + branchingQuality: number; + deploymentSafety: number; + riskLevel: RiskLevel; + scoreExplanations: Record; +} + +// ─── Deployment ─────────────────────────────────────────────────────────────── + +export interface DeploymentResult { + jobId: string; + n8nWorkflowId?: string; + status: 'success' | 'failed'; + deployedAt: string; + errorMessage?: string; +} + +export interface ActivationResult { + jobId: string; + n8nWorkflowId: string; + activationStatus: 'activated' | 'failed'; + finalValidationPassed: boolean; + finalCredentialCheckPassed: boolean; + activatedAt: string; + diagnostics?: string; +} + +export interface ApprovalRequest { + jobId: string; + requestedAt: string; + expiresAt: string; + architectureSummary: string; + riskSummary: string; + readinessScore: number; + validationScore: number; + simulationScore: number; + credentialStatus: 'complete' | 'incomplete'; + status: 'pending' | 'approved' | 'rejected'; + reviewerNote?: string; + respondedAt?: string; +} + +// ─── Audit ──────────────────────────────────────────────────────────────────── + +export interface AuditEvent { + id: string; + jobId: string; + event: string; + actor: 'user' | 'ai' | 'system'; + timestamp: string; + data?: Record; +} + +// ─── Swarm Result Summary ───────────────────────────────────────────────────── + +export interface SwarmResultSummary { + swarmSize: number; + generatedAt: string; + winnerStrategy: string; + winnerScore: number; + candidateScores: Array<{ id: string; strategy: string; score: number }>; + selectionReason?: string; +} + +// ─── Trigger Detection ──────────────────────────────────────────────────────── + +export interface TriggerDetectionResult { + nodeType: string; + triggerType: string; + confidence: 'high' | 'medium' | 'low'; + webhookPath?: string; + notes: string; +} + +// ─── Self-Healing Summary ───────────────────────────────────────────────────── + +export interface HealingResultSummary { + healed: boolean; + actionsApplied: Array<{ + type: string; + targetNodeId?: string; + description: string; + applied: boolean; + }>; + remainingIssues: string[]; + confidence: number; +} + +// ─── Job ───────────────────────────────────────────────────────────────────── + +export interface WorkflowJob { + id: string; + userRequest: string; + state: WorkflowLifecycleState; + createdAt: string; + updatedAt: string; + intent?: WorkflowIntent; + architecturePlan?: WorkflowArchitecturePlan; + graph?: WorkflowGraph; + compiledWorkflow?: N8nWorkflow; + validationReport?: ValidationReport; + simulationReport?: SimulationReport; + credentialAnalysis?: CredentialAnalysis; + deploymentResult?: DeploymentResult; + activationResult?: ActivationResult; + approvalRequest?: ApprovalRequest; + qualityScore?: WorkflowQualityScore; // NEW + healingResult?: HealingResultSummary; // NEW + swarmResult?: SwarmResultSummary; // NEW + triggerDetection?: TriggerDetectionResult; // NEW + auditLog: AuditEvent[]; + error?: string; +} + +// ─── API Response ───────────────────────────────────────────────────────────── + +export interface GenerateWorkflowResponse { + success: boolean; + jobId: string; + state: 'generated' | 'validated' | 'simulated'; + message: string; + architecturePlan?: WorkflowArchitecturePlan; + graph?: WorkflowGraph; + compiledWorkflow?: N8nWorkflow; + validationReport?: ValidationReport; + simulationReport?: SimulationReport; + credentialAnalysis?: CredentialAnalysis; + deploymentReadiness?: DeploymentReadiness; + qualityScore?: WorkflowQualityScore; + healingResult?: HealingResultSummary; + swarmResult?: SwarmResultSummary; + triggerDetection?: TriggerDetectionResult; + memoryStats?: { + totalPatterns: number; + successPatterns: number; + failurePatterns: number; + templates: number; + avgSuccessScore: number; + fixStrategies: number; + }; +} diff --git a/apps/worker/src/utils/audit.ts b/apps/worker/src/utils/audit.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e9d0c73d225ec35a0efb7e03a0a497368a20891 --- /dev/null +++ b/apps/worker/src/utils/audit.ts @@ -0,0 +1,22 @@ +/** + * Audit Event Factory + */ +import type { AuditEvent } from '../types/workflow'; + +let counter = 0; + +export function createAuditEvent( + jobId: string, + event: string, + actor: AuditEvent['actor'], + data?: Record, +): AuditEvent { + return { + id: `audit-${jobId}-${++counter}-${Date.now()}`, + jobId, + event, + actor, + timestamp: new Date().toISOString(), + data, + }; +} diff --git a/apps/worker/src/utils/ids.ts b/apps/worker/src/utils/ids.ts new file mode 100644 index 0000000000000000000000000000000000000000..a1fedd4402578e21ad4fa7993d835503f07a23f4 --- /dev/null +++ b/apps/worker/src/utils/ids.ts @@ -0,0 +1,12 @@ +/** + * ID Generation Utility + */ +export function generateJobId(): string { + const timestamp = Date.now().toString(36); + const random = Math.random().toString(36).slice(2, 8); + return `wf_${timestamp}_${random}`; +} + +export function generateNodeId(prefix = 'node'): string { + return `${prefix}_${Math.random().toString(36).slice(2, 10)}`; +} diff --git a/apps/worker/tsconfig.json b/apps/worker/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..182ec5ff96d6f26ee0031a9497255e82925f5eeb --- /dev/null +++ b/apps/worker/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["@cloudflare/workers-types"], + "outDir": "./dist", + "rootDir": "./src", + "noEmit": true + }, + "include": ["src/**/*"] +} diff --git a/apps/worker/wrangler.toml b/apps/worker/wrangler.toml new file mode 100644 index 0000000000000000000000000000000000000000..67c79561a59cd8728c27a7d1118227c13f0a465b --- /dev/null +++ b/apps/worker/wrangler.toml @@ -0,0 +1,55 @@ +name = "workflow-factor-os" +main = "src/index.ts" +compatibility_date = "2024-11-01" +compatibility_flags = ["nodejs_compat"] + +# ─── Environment ────────────────────────────────────────────────────────────── +[vars] +ENVIRONMENT = "production" +SIMULATOR_URL = "https://your-simulator-service.com" +N8N_BASE_URL = "https://your-n8n-instance.com" + +# ─── LLM Gateway (provider-agnostic) ───────────────────────────────────────── +# Set LLM_PROVIDER to: openai | anthropic | openai-compatible | custom +# Set LLM_GATEWAY_URL to your endpoint (default: https://api.openai.com/v1) +# Set LLM_MODEL to your preferred model (e.g. gpt-4o, claude-3-5-sonnet-20241022) +LLM_PROVIDER = "openai" +LLM_GATEWAY_URL = "https://api.openai.com/v1" +LLM_MODEL = "gpt-4o" + +# ─── KV Namespaces ──────────────────────────────────────────────────────────── +# Create via: wrangler kv:namespace create "WFO_CACHE" +[[kv_namespaces]] +binding = "WFO_CACHE" +id = "REPLACE_WITH_YOUR_KV_ID" +preview_id = "REPLACE_WITH_YOUR_PREVIEW_KV_ID" + +# ─── Secrets (set via: wrangler secret put SECRET_NAME) ────────────────────── +# LLM_API_KEY → Your LLM provider API key (OpenAI, Anthropic, or custom) +# OPENAI_API_KEY → Legacy: use LLM_API_KEY instead (still supported) +# LLM_FALLBACK_URL → Optional fallback LLM gateway URL +# LLM_FALLBACK_KEY → Optional fallback LLM API key +# LLM_FALLBACK_MODEL → Optional fallback model name +# N8N_API_KEY → n8n REST API key +# INTERNAL_API_SECRET → Shared secret between Worker ↔ Simulator + +# ─── Resource Limits ───────────────────────────────────────────────────────── +[limits] +cpu_ms = 10 + +# ─── Routes (update with your actual domain) ───────────────────────────────── +# [[routes]] +# pattern = "wfo.yourdomain.com/*" +# zone_name = "yourdomain.com" + +# ─── Development environment overrides ─────────────────────────────────────── +[env.development] +vars = { ENVIRONMENT = "development", SIMULATOR_URL = "http://localhost:3001", N8N_BASE_URL = "http://localhost:5678", LLM_PROVIDER = "openai", LLM_GATEWAY_URL = "https://api.openai.com/v1", LLM_MODEL = "gpt-4o" } + +# ─── Production environment ─────────────────────────────────────────────────── +[env.production] +vars = { ENVIRONMENT = "production" } + +[[env.production.kv_namespaces]] +binding = "WFO_CACHE" +id = "REPLACE_WITH_PRODUCTION_KV_ID" diff --git a/config/env/.env.example b/config/env/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..fbe6f4b7b42d8c77b64a953a44c619496bd9a0f7 --- /dev/null +++ b/config/env/.env.example @@ -0,0 +1,47 @@ +# ─── Workflow Factor OS — Environment Configuration ────────────────────────── +# Copy this file to .env and fill in your values + +# ─── LLM Gateway (Provider-Agnostic) ───────────────────────────────────────── +# Choose your LLM provider: openai | anthropic | openai-compatible | custom +LLM_PROVIDER=openai + +# Your LLM provider API key +LLM_API_KEY=your-api-key-here + +# LLM Gateway URL (default: OpenAI) +# For Anthropic: https://api.anthropic.com +# For OpenAI: https://api.openai.com/v1 +# For custom: https://your-custom-llm-gateway.com/v1 +LLM_GATEWAY_URL=https://api.openai.com/v1 + +# Model to use (switch without code changes) +# OpenAI: gpt-4o | gpt-4o-mini | gpt-4-turbo +# Anthropic: claude-3-5-sonnet-20241022 | claude-3-haiku-20240307 +# Custom: depends on your provider +LLM_MODEL=gpt-4o + +# Optional: Fallback LLM provider (used if primary fails) +# LLM_FALLBACK_URL=https://api.anthropic.com +# LLM_FALLBACK_KEY=your-fallback-key +# LLM_FALLBACK_MODEL=claude-3-haiku-20240307 + +# ─── Legacy (Backward Compatibility) ───────────────────────────────────────── +# Deprecated: use LLM_API_KEY instead. Still supported for existing deployments. +# OPENAI_API_KEY=your-openai-key + +# ─── n8n Instance ──────────────────────────────────────────────────────────── +N8N_BASE_URL=http://localhost:5678 +N8N_API_KEY=your-n8n-api-key-here + +# ─── Simulator Service ──────────────────────────────────────────────────────── +PORT=3001 +INTERNAL_API_SECRET=your-shared-secret-between-worker-and-simulator +WORKER_ORIGIN=* + +# ─── Logging ───────────────────────────────────────────────────────────────── +LOG_LEVEL=info + +# ─── Swarm Configuration ───────────────────────────────────────────────────── +# Number of parallel workflow designs generated (1-5, default: 3) +# Higher = better quality but more LLM API usage +# SWARM_DEFAULT_SIZE=3 diff --git a/config/n8n/api.ts b/config/n8n/api.ts new file mode 100644 index 0000000000000000000000000000000000000000..afb8864a71c8f923052140bcdbb0852622c47509 --- /dev/null +++ b/config/n8n/api.ts @@ -0,0 +1,17 @@ +/** + * n8n REST API Client Configuration + */ +export const N8N_API_ENDPOINTS = { + workflows: '/api/v1/workflows', + workflow: (id: string) => `/api/v1/workflows/${id}`, + activateWorkflow: (id: string) => `/api/v1/workflows/${id}/activate`, + deactivateWorkflow: (id: string) => `/api/v1/workflows/${id}/deactivate`, + credentials: '/api/v1/credentials', + executions: '/api/v1/executions', + execution: (id: string) => `/api/v1/executions/${id}`, +} as const; + +export const N8N_DEFAULT_HEADERS = (apiKey: string) => ({ + 'Content-Type': 'application/json', + 'X-N8N-API-KEY': apiKey, +}); diff --git a/config/prompts/index.ts b/config/prompts/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e09a8a230c7ecd77aaab0438d6ef938096870f5 --- /dev/null +++ b/config/prompts/index.ts @@ -0,0 +1,9 @@ +/** + * Config: Prompt Registry + * Central registry for all agent prompts + */ +export { INTENT_INTERPRETER_PROMPT } from '../../apps/simulator/src/prompts/intentInterpreter'; +export { WORKFLOW_PLANNER_PROMPT } from '../../apps/simulator/src/prompts/workflowPlanner'; +export { GRAPH_ENGINE_PROMPT } from '../../apps/simulator/src/prompts/graphEngine'; +export { COMPILER_PROMPT } from '../../apps/simulator/src/prompts/compiler'; +export { SIMULATOR_PROMPT } from '../../apps/simulator/src/prompts/simulator'; diff --git a/integrations/llm-providers/index.ts b/integrations/llm-providers/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1ac367c4fcc296045312d3920afff5b4e807aa5 --- /dev/null +++ b/integrations/llm-providers/index.ts @@ -0,0 +1,388 @@ +/** + * LLM Gateway - Provider-Agnostic LLM Interface + * Supports: OpenAI-compatible, Anthropic, custom endpoints + * Features: retry, fallback, timeout, model switching, provider routing + * NO direct OpenAI SDK dependency - pure fetch-based + */ + +export interface LLMMessage { + role: 'system' | 'user' | 'assistant'; + content: string; +} + +export interface LLMRequestOptions { + model?: string; + temperature?: number; + maxTokens?: number; + responseFormat?: 'json_object' | 'text'; + timeout?: number; + retries?: number; + retryDelayMs?: number; +} + +export interface LLMResponse { + content: string; + model: string; + provider: string; + usage?: { + promptTokens: number; + completionTokens: number; + totalTokens: number; + }; + latencyMs: number; +} + +export interface LLMProviderConfig { + provider: 'openai' | 'anthropic' | 'openai-compatible' | 'custom'; + baseUrl: string; + apiKey: string; + defaultModel: string; + maxTokens?: number; + timeout?: number; +} + +export interface LLMGatewayConfig { + primary: LLMProviderConfig; + fallback?: LLMProviderConfig; + defaultRetries?: number; + defaultRetryDelayMs?: number; + defaultTimeoutMs?: number; +} + +// ─── Default model configurations ──────────────────────────────────────────── +export const DEFAULT_MODELS = { + openai: { + fast: 'gpt-4o-mini', + balanced: 'gpt-4o', + powerful: 'gpt-4o', + }, + anthropic: { + fast: 'claude-3-haiku-20240307', + balanced: 'claude-3-5-sonnet-20241022', + powerful: 'claude-3-opus-20240229', + }, +} as const; + +// ─── LLM Gateway Client ─────────────────────────────────────────────────────── +export class LLMGateway { + private config: LLMGatewayConfig; + + constructor(config: LLMGatewayConfig) { + this.config = { + defaultRetries: 3, + defaultRetryDelayMs: 1000, + defaultTimeoutMs: 60000, + ...config, + }; + } + + /** + * Create from environment variables (provider-agnostic) + */ + static fromEnv(env: { + LLM_GATEWAY_URL?: string; + LLM_API_KEY?: string; + LLM_MODEL?: string; + LLM_PROVIDER?: string; + LLM_FALLBACK_URL?: string; + LLM_FALLBACK_KEY?: string; + LLM_FALLBACK_MODEL?: string; + // Legacy OpenAI support + OPENAI_API_KEY?: string; + }): LLMGateway { + const provider = (env.LLM_PROVIDER ?? 'openai') as LLMProviderConfig['provider']; + const baseUrl = env.LLM_GATEWAY_URL ?? 'https://api.openai.com/v1'; + const apiKey = env.LLM_API_KEY ?? env.OPENAI_API_KEY ?? ''; + const defaultModel = env.LLM_MODEL ?? 'gpt-4o'; + + const primary: LLMProviderConfig = { provider, baseUrl, apiKey, defaultModel }; + + let fallback: LLMProviderConfig | undefined; + if (env.LLM_FALLBACK_URL && env.LLM_FALLBACK_KEY) { + fallback = { + provider: 'openai-compatible', + baseUrl: env.LLM_FALLBACK_URL, + apiKey: env.LLM_FALLBACK_KEY, + defaultModel: env.LLM_FALLBACK_MODEL ?? defaultModel, + }; + } + + return new LLMGateway({ primary, fallback }); + } + + /** + * Main completion method - provider-agnostic + */ + async complete( + messages: LLMMessage[], + options: LLMRequestOptions = {}, + ): Promise { + const retries = options.retries ?? this.config.defaultRetries ?? 3; + + // Try primary provider + try { + return await this.completeWithRetry(this.config.primary, messages, options, retries); + } catch (primaryErr) { + // Try fallback if available + if (this.config.fallback) { + console.warn(`[LLMGateway] Primary provider failed, trying fallback: ${primaryErr}`); + try { + return await this.completeWithRetry(this.config.fallback, messages, options, 2); + } catch (fallbackErr) { + throw new Error( + `Both primary and fallback LLM providers failed.\nPrimary: ${primaryErr}\nFallback: ${fallbackErr}`, + ); + } + } + throw primaryErr; + } + } + + /** + * Complete with JSON response format enforced + */ + async completeJSON>( + messages: LLMMessage[], + options: LLMRequestOptions = {}, + ): Promise { + const response = await this.complete(messages, { + ...options, + responseFormat: 'json_object', + temperature: options.temperature ?? 0.1, + }); + + try { + return JSON.parse(response.content) as T; + } catch { + // Try to extract JSON from markdown code blocks + const jsonMatch = response.content.match(/```(?:json)?\s*([\s\S]+?)\s*```/); + if (jsonMatch?.[1]) { + try { + return JSON.parse(jsonMatch[1]) as T; + } catch { + // fall through + } + } + throw new Error( + `LLM returned non-parseable JSON.\nContent: ${response.content.slice(0, 500)}`, + ); + } + } + + // ─── Internal retry loop ────────────────────────────────────────────────── + private async completeWithRetry( + provider: LLMProviderConfig, + messages: LLMMessage[], + options: LLMRequestOptions, + maxRetries: number, + ): Promise { + let lastError: Error | undefined; + const delay = options.retryDelayMs ?? this.config.defaultRetryDelayMs ?? 1000; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + if (attempt > 0) { + const backoff = delay * Math.pow(2, attempt - 1); // exponential backoff + await sleep(Math.min(backoff, 30000)); + } + + try { + return await this.callProvider(provider, messages, options); + } catch (err) { + lastError = err instanceof Error ? err : new Error(String(err)); + + // Don't retry on auth errors or bad requests + if (lastError.message.includes('401') || lastError.message.includes('400')) { + throw lastError; + } + + if (attempt < maxRetries) { + console.warn(`[LLMGateway] Attempt ${attempt + 1}/${maxRetries + 1} failed: ${lastError.message}. Retrying...`); + } + } + } + + throw lastError ?? new Error('LLM call failed after retries'); + } + + // ─── Provider-specific call ─────────────────────────────────────────────── + private async callProvider( + provider: LLMProviderConfig, + messages: LLMMessage[], + options: LLMRequestOptions, + ): Promise { + const startTime = Date.now(); + const timeout = options.timeout ?? provider.timeout ?? this.config.defaultTimeoutMs ?? 60000; + const model = options.model ?? provider.defaultModel; + + if (provider.provider === 'anthropic') { + return this.callAnthropic(provider, messages, options, model, timeout, startTime); + } + + // OpenAI-compatible (default for openai, openai-compatible, custom) + return this.callOpenAICompatible(provider, messages, options, model, timeout, startTime); + } + + // ─── OpenAI-compatible API ──────────────────────────────────────────────── + private async callOpenAICompatible( + provider: LLMProviderConfig, + messages: LLMMessage[], + options: LLMRequestOptions, + model: string, + timeout: number, + startTime: number, + ): Promise { + const body: Record = { + model, + messages, + temperature: options.temperature ?? 0.1, + max_tokens: options.maxTokens ?? provider.maxTokens ?? 4096, + }; + + if (options.responseFormat === 'json_object') { + body['response_format'] = { type: 'json_object' }; + } + + const response = await fetch(`${provider.baseUrl}/chat/completions`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${provider.apiKey}`, + }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(timeout), + }); + + if (!response.ok) { + const errText = await response.text().catch(() => ''); + throw new Error(`LLM API error ${response.status}: ${errText}`); + } + + const data = await response.json() as { + choices: Array<{ message: { content: string } }>; + model: string; + usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }; + }; + + const content = data.choices[0]?.message?.content ?? ''; + if (!content) throw new Error('LLM returned empty content'); + + return { + content, + model: data.model ?? model, + provider: provider.provider, + usage: data.usage + ? { + promptTokens: data.usage.prompt_tokens, + completionTokens: data.usage.completion_tokens, + totalTokens: data.usage.total_tokens, + } + : undefined, + latencyMs: Date.now() - startTime, + }; + } + + // ─── Anthropic API ──────────────────────────────────────────────────────── + private async callAnthropic( + provider: LLMProviderConfig, + messages: LLMMessage[], + options: LLMRequestOptions, + model: string, + timeout: number, + startTime: number, + ): Promise { + // Extract system message for Anthropic's system parameter + const systemMessage = messages.find((m) => m.role === 'system')?.content ?? ''; + const userMessages = messages.filter((m) => m.role !== 'system'); + + // For JSON mode with Anthropic, add explicit instruction + const finalMessages = options.responseFormat === 'json_object' + ? [ + ...userMessages, + { + role: 'assistant' as const, + content: 'Here is the JSON response:\n{', + }, + ] + : userMessages; + + const body: Record = { + model, + max_tokens: options.maxTokens ?? provider.maxTokens ?? 4096, + temperature: options.temperature ?? 0.1, + messages: finalMessages, + }; + + if (systemMessage) body['system'] = systemMessage; + + const response = await fetch(`${provider.baseUrl}/messages`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': provider.apiKey, + 'anthropic-version': '2023-06-01', + }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(timeout), + }); + + if (!response.ok) { + const errText = await response.text().catch(() => ''); + throw new Error(`Anthropic API error ${response.status}: ${errText}`); + } + + const data = await response.json() as { + content: Array<{ type: string; text: string }>; + model: string; + usage?: { input_tokens: number; output_tokens: number }; + }; + + let content = data.content[0]?.text ?? ''; + if (!content) throw new Error('Anthropic returned empty content'); + + // If we injected "{" for JSON mode, prepend it + if (options.responseFormat === 'json_object' && !content.trim().startsWith('{')) { + content = '{' + content; + } + + return { + content, + model: data.model ?? model, + provider: 'anthropic', + usage: data.usage + ? { + promptTokens: data.usage.input_tokens, + completionTokens: data.usage.output_tokens, + totalTokens: data.usage.input_tokens + data.usage.output_tokens, + } + : undefined, + latencyMs: Date.now() - startTime, + }; + } +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// ─── Singleton factory for simulator service ────────────────────────────────── +let _gatewayInstance: LLMGateway | null = null; + +export function getLLMGateway(env: { + LLM_GATEWAY_URL?: string; + LLM_API_KEY?: string; + LLM_MODEL?: string; + LLM_PROVIDER?: string; + LLM_FALLBACK_URL?: string; + LLM_FALLBACK_KEY?: string; + LLM_FALLBACK_MODEL?: string; + OPENAI_API_KEY?: string; +}): LLMGateway { + if (!_gatewayInstance) { + _gatewayInstance = LLMGateway.fromEnv(env); + } + return _gatewayInstance; +} + +export function createLLMGateway(config: LLMGatewayConfig): LLMGateway { + return new LLMGateway(config); +} diff --git a/integrations/n8n-client/index.ts b/integrations/n8n-client/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6bc1672349cebef6c3abe7031a86274ce03d370c --- /dev/null +++ b/integrations/n8n-client/index.ts @@ -0,0 +1,84 @@ +/** + * n8n API Client Integration + * Typed client for interacting with the n8n REST API + */ +import { N8N_API_ENDPOINTS } from '../../config/n8n/api'; +import type { N8nWorkflow, AvailableCredential } from '../../apps/worker/src/types/workflow'; + +export class N8nClient { + private baseUrl: string; + private apiKey: string; + + constructor(baseUrl: string, apiKey: string) { + this.baseUrl = baseUrl.replace(/\/$/, ''); + this.apiKey = apiKey; + } + + private get headers() { + return { + 'Content-Type': 'application/json', + 'X-N8N-API-KEY': this.apiKey, + }; + } + + async createWorkflow(workflow: N8nWorkflow): Promise<{ id: string }> { + const resp = await fetch(`${this.baseUrl}${N8N_API_ENDPOINTS.workflows}`, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ ...workflow, active: false }), + signal: AbortSignal.timeout(10000), + }); + if (!resp.ok) throw new Error(`n8n create workflow failed: ${resp.status} ${await resp.text()}`); + return resp.json() as Promise<{ id: string }>; + } + + async updateWorkflow(id: string, workflow: N8nWorkflow): Promise { + const resp = await fetch(`${this.baseUrl}${N8N_API_ENDPOINTS.workflow(id)}`, { + method: 'PUT', + headers: this.headers, + body: JSON.stringify({ ...workflow, active: false }), + signal: AbortSignal.timeout(10000), + }); + if (!resp.ok) throw new Error(`n8n update workflow failed: ${resp.status} ${await resp.text()}`); + } + + async activateWorkflow(id: string): Promise { + const resp = await fetch(`${this.baseUrl}${N8N_API_ENDPOINTS.activateWorkflow(id)}`, { + method: 'POST', + headers: this.headers, + signal: AbortSignal.timeout(10000), + }); + if (!resp.ok) throw new Error(`n8n activate workflow failed: ${resp.status} ${await resp.text()}`); + } + + async deactivateWorkflow(id: string): Promise { + const resp = await fetch(`${this.baseUrl}${N8N_API_ENDPOINTS.deactivateWorkflow(id)}`, { + method: 'POST', + headers: this.headers, + signal: AbortSignal.timeout(10000), + }); + if (!resp.ok) throw new Error(`n8n deactivate workflow failed: ${resp.status} ${await resp.text()}`); + } + + async getCredentials(): Promise { + const resp = await fetch(`${this.baseUrl}${N8N_API_ENDPOINTS.credentials}`, { + headers: this.headers, + signal: AbortSignal.timeout(8000), + }); + if (!resp.ok) throw new Error(`n8n get credentials failed: ${resp.status}`); + const data = await resp.json() as { data: AvailableCredential[] }; + return data.data ?? []; + } + + async healthCheck(): Promise { + try { + const resp = await fetch(`${this.baseUrl}/api/v1/workflows?limit=1`, { + headers: this.headers, + signal: AbortSignal.timeout(5000), + }); + return resp.ok; + } catch { + return false; + } + } +} diff --git a/knowledge/workflow-patterns/index.ts b/knowledge/workflow-patterns/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..210418fe83cce80ee9feb0b64c95f83cc9332604 --- /dev/null +++ b/knowledge/workflow-patterns/index.ts @@ -0,0 +1,124 @@ +/** + * Workflow Patterns Knowledge Base + * Production-grade patterns for senior-level workflow engineering + * Used by Planner and Compiler to apply best practices + */ + +export const WORKFLOW_PATTERNS = { + + // ─── Retry Patterns ──────────────────────────────────────────────────────── + retry: { + exponentialBackoff: { + name: 'Exponential Backoff Retry', + description: 'Retries with increasing delay to avoid overwhelming services', + n8nConfig: { retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 }, + useWhen: 'External API calls, LLM requests, database writes', + }, + linearRetry: { + name: 'Linear Retry', + description: 'Fixed delay retries for idempotent operations', + n8nConfig: { retryOnFail: true, maxTries: 5, waitBetweenTries: 2000 }, + useWhen: 'File operations, simple CRUD, idempotent webhooks', + }, + }, + + // ─── Webhook Safety Patterns ────────────────────────────────────────────── + webhookSafety: { + validateAndEnqueue: { + name: 'Validate → Enqueue → Acknowledge', + description: 'Webhook receives, validates, enqueues for async processing, returns 200 quickly', + steps: ['Webhook Trigger', 'Validate Signature', 'Validate Schema', 'Generate Idempotency Key', 'Check Processed', 'Enqueue Job', 'Return 200'], + avoids: 'Webhook timeout, duplicate processing, heavy sync processing', + }, + idempotencyGuard: { + name: 'Idempotency Guard', + description: 'Prevents duplicate processing using request fingerprinting', + steps: ['Hash request body+timestamp', 'Check KV/DB for existing hash', 'Skip if already processed', 'Store hash after processing'], + }, + }, + + // ─── AI Workflow Patterns ───────────────────────────────────────────────── + aiWorkflow: { + validateAiOutput: { + name: 'AI Output Validation', + description: 'Validates AI output schema before using downstream', + steps: ['Call LLM', 'Check output is valid JSON', 'Validate against expected schema', 'Retry with different prompt if invalid', 'Fallback to safe default if all retries fail'], + }, + moderationGate: { + name: 'Content Moderation Gate', + description: 'Moderate AI output before sending to users', + steps: ['AI generates content', 'Run moderation API', 'Block if flagged', 'Log moderation events', 'Allow if safe'], + }, + }, + + // ─── Error Handling Patterns ────────────────────────────────────────────── + errorHandling: { + deadLetterQueue: { + name: 'Dead Letter Queue', + description: 'Failed items are stored for manual review/reprocessing', + steps: ['Process item', 'On max retry exhaustion', 'Write to DLQ table/sheet', 'Alert on-call via notification', 'Dashboard shows DLQ items'], + }, + circuitBreaker: { + name: 'Circuit Breaker', + description: 'Stop hammering a failing service after threshold', + steps: ['Track failure count in KV', 'If count > threshold → open circuit', 'Return cached response or fallback', 'Probe service after timeout → half-open', 'Reset on success'], + }, + }, + + // ─── Observability Patterns ─────────────────────────────────────────────── + observability: { + structuredLogging: { + name: 'Structured Logging', + description: 'Every significant action logs jobId, nodeId, timestamp, status', + implementation: 'Code node at monitoring layer outputs structured log JSON to webhook/external log service', + }, + executionNotification: { + name: 'Execution Completion Notification', + description: 'Notify on workflow success/failure', + steps: ['After final node', 'Compose summary message', 'Send to Slack/Telegram channel'], + }, + }, + + // ─── Approval Patterns ──────────────────────────────────────────────────── + approval: { + humanApproval: { + name: 'Human-In-The-Loop Approval', + description: 'Pause workflow and wait for human decision', + steps: ['Generate approval request', 'Send notification with approve/reject link', 'Wait for webhook callback', 'Route based on decision'], + }, + }, + + // ─── Anti-Patterns ──────────────────────────────────────────────────────── + antiPatterns: [ + { + name: 'Monolithic Workflow', + description: '200+ nodes in one workflow with no separation of concerns', + fix: 'Split into subworkflows by responsibility domain', + }, + { + name: 'Silent Failure', + description: 'Workflow fails without any notification or logging', + fix: 'Add error notification nodes and error workflow setting', + }, + { + name: 'Direct Webhook Processing', + description: 'Heavy processing inside webhook request path causing timeouts', + fix: 'Use validate → enqueue → acknowledge pattern', + }, + { + name: 'Unsafe AI Trust', + description: 'Using AI output directly without validation', + fix: 'Always validate AI output schema before downstream use', + }, + { + name: 'Credential Hardcoding', + description: 'API keys in Code nodes or Set node values', + fix: 'Always use n8n credential system', + }, + { + name: 'No Retry Policy', + description: 'External API calls without retry configuration', + fix: 'Add retryOnFail: true to all external API nodes', + }, + ], +}; diff --git a/observability/audit/index.ts b/observability/audit/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4465be72e48617900a2dc577207288a2b44172a2 --- /dev/null +++ b/observability/audit/index.ts @@ -0,0 +1,29 @@ +/** + * Observability - Audit Trail + * Tracks all workflow lifecycle events for compliance and debugging + */ +import type { AuditEvent } from '../../apps/worker/src/types/workflow'; + +export class AuditTrail { + private events: AuditEvent[] = []; + + record(event: AuditEvent): void { + this.events.push(event); + } + + getEvents(): AuditEvent[] { + return [...this.events]; + } + + getEventsByJobId(jobId: string): AuditEvent[] { + return this.events.filter((e) => e.jobId === jobId); + } + + toReport(): Record { + return { + totalEvents: this.events.length, + events: this.events, + generatedAt: new Date().toISOString(), + }; + } +} diff --git a/observability/logging/index.ts b/observability/logging/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..bcb4d96eaa0e08b3e5f8d2dfa0742e945773c20c --- /dev/null +++ b/observability/logging/index.ts @@ -0,0 +1,31 @@ +/** + * Observability - Structured Logger + * Production-grade logging for the Simulator service + */ +// @ts-ignore +import pino from 'pino'; + +export const createLogger = (service: string) => + pino({ + transport: { + target: 'pino-pretty', + options: { colorize: true }, + }, + level: process.env['LOG_LEVEL'] ?? 'info', + base: { service }, + }); + +export function buildAuditEvent(params: { + jobId: string; + event: string; + actor: 'system' | 'user' | 'ai'; + data: Record; + level?: 'info' | 'warn' | 'error'; +}) { + return { + id: `audit_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, + ...params, + level: params.level ?? 'info', + timestamp: new Date().toISOString(), + }; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..1666ebc708832f1b475a50521704737ec2282f35 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5796 @@ +{ + "name": "workflow-factor-os", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "workflow-factor-os", + "version": "1.0.0", + "workspaces": [ + "apps/*", + "core/*", + "integrations/*" + ], + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "concurrently": "^8.0.0", + "eslint": "^8.0.0", + "typescript": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "apps/simulator": { + "name": "@wfo/simulator", + "version": "1.0.0", + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.0", + "express": "^4.18.0", + "express-rate-limit": "^7.2.0", + "helmet": "^7.1.0", + "module-alias": "^2.3.4", + "openai": "^4.28.0", + "pino": "^8.17.0", + "pino-pretty": "^10.3.0", + "zod": "^3.22.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^20.11.0", + "tsx": "^4.7.0", + "typescript": "^5.3.0" + } + }, + "apps/worker": { + "name": "@wfo/worker", + "version": "1.0.0", + "dependencies": { + "hono": "^4.0.0", + "zod": "^3.22.0" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240208.0", + "typescript": "^5.3.0", + "wrangler": "^3.28.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", + "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/@cloudflare/kv-asset-handler/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", + "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.14", + "workerd": "^1.20250124.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz", + "integrity": "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz", + "integrity": "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz", + "integrity": "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz", + "integrity": "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz", + "integrity": "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20260511.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260511.1.tgz", + "integrity": "sha512-FA+si7cOq9i/gtCHhIc0XJL0l1F/ApF+m00752Aj7WZFJrj3ZulT2T8/+rT3BabMT0QEnqFEGIqCgrmqhgEfMg==", + "dev": true, + "license": "MIT OR Apache-2.0" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild-plugins/node-globals-polyfill": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", + "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild-plugins/node-modules-polyfill": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", + "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", + "dev": true, + "license": "ISC", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "rollup-plugin-node-polyfills": "^0.2.1" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/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.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@wfo/simulator": { + "resolved": "apps/simulator", + "link": true + }, + "node_modules/@wfo/worker": { + "resolved": "apps/worker", + "link": true + }, + "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==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "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/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-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/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "printable-characters": "^1.0.42" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "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/body-parser/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/body-parser/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/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "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==", + "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/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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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/chalk/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/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "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/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.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true, + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/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/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/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/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/express/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/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.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-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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/finalhandler/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/finalhandler/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/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "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": "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/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz", + "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/hono": { + "version": "4.12.18", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "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/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "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/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "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-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "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-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-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "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.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "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": "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/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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/miniflare": { + "version": "3.20250718.3", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250718.3.tgz", + "integrity": "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "8.14.0", + "acorn-walk": "8.3.2", + "exit-hook": "2.2.1", + "glob-to-regexp": "0.4.1", + "stoppable": "1.1.0", + "undici": "^5.28.5", + "workerd": "1.20250718.0", + "ws": "8.18.0", + "youch": "3.3.4", + "zod": "3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/miniflare/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/miniflare/node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/module-alias": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.3.4.tgz", + "integrity": "sha512-bOclZt8hkpuGgSSoG07PKmvzTizROilUTvLNyrMqvlC9snhs7y7GzjNWAVbISIOlhCP1T14rH1PDAV9iNyBq/w==", + "license": "MIT" + }, + "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/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-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/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/pino": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", + "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.6.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", + "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "dev": true, + "license": "Unlicense" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "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/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "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/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/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "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/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "node_modules/rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "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/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/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/send/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/send/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/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/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "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/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stacktracey": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.2.0.tgz", + "integrity": "sha512-ETyQEz+CzXiLjEbyJqpbp+/T79RQD/6wqFucRBIlVNZfYq2Ay7wbretD4cxpbymZlaPWx58aIhPEY1Cr8DlVvg==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, + "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/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "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/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-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thread-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "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/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.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==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/unenv": { + "version": "2.0.0-rc.14", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz", + "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.1", + "ohash": "^2.0.10", + "pathe": "^2.0.3", + "ufo": "^1.5.4" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/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/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/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerd": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250718.0.tgz", + "integrity": "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20250718.0", + "@cloudflare/workerd-darwin-arm64": "1.20250718.0", + "@cloudflare/workerd-linux-64": "1.20250718.0", + "@cloudflare/workerd-linux-arm64": "1.20250718.0", + "@cloudflare/workerd-windows-64": "1.20250718.0" + } + }, + "node_modules/wrangler": { + "version": "3.114.17", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.17.tgz", + "integrity": "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.3.4", + "@cloudflare/unenv-preset": "2.0.2", + "@esbuild-plugins/node-globals-polyfill": "0.2.3", + "@esbuild-plugins/node-modules-polyfill": "0.2.2", + "blake3-wasm": "2.1.5", + "esbuild": "0.17.19", + "miniflare": "3.20250718.3", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.14", + "workerd": "1.20250718.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=16.17.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20250408.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/wrangler/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/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.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "devOptional": true, + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/youch": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", + "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie": "^0.7.1", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + } + }, + "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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..822c84f3f15c9d10b4e6321c1e9c9d5d0d7cb513 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "workflow-factor-os", + "version": "1.0.0", + "description": "Production-Grade AI Workflow Orchestrator for n8n - Cloudflare Workers Architecture", + "private": true, + "workspaces": [ + "apps/*", + "core/*", + "integrations/*" + ], + "scripts": { + "dev": "concurrently \"npm run dev:worker\" \"npm run dev:simulator\"", + "dev:worker": "npm run dev --workspace=apps/worker", + "dev:simulator": "npm run dev --workspace=apps/simulator", + "build": "npm run build --workspaces --if-present", + "test": "npm run test --workspaces --if-present", + "lint": "eslint . --ext .ts,.tsx", + "typecheck": "tsc --noEmit", + "deploy:worker": "npm run deploy --workspace=apps/worker", + "setup:knowledge": "node scripts/build-knowledge-base.js", + "validate:env": "node scripts/validate-env.js" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "concurrently": "^8.0.0", + "eslint": "^8.0.0", + "typescript": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b89903b22ae38eb4e9d1edab4c9518a695fea65e --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1243 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@typescript-eslint/eslint-plugin': + specifier: ^6.0.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^6.0.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) + concurrently: + specifier: ^8.0.0 + version: 8.2.2 + eslint: + specifier: ^8.0.0 + version: 8.57.1 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + +packages: + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + 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 + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@babel/runtime@7.29.2': {} + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@types/json-schema@7.0.15': {} + + '@types/semver@7.7.1': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.8.0 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.8.0 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + eslint: 8.57.1 + semver: 7.8.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.1': {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.18.1 + rxjs: 7.8.2 + shell-quote: 1.8.3 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.29.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + emoji-regex@8.0.0: {} + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.1 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.4.2: {} + + fs.realpath@1.0.0: {} + + get-caller-file@2.0.5: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + isexe@2.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.18.1: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.1.0 + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-type@4.0.0: {} + + picomatch@2.3.2: {} + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + require-directory@2.1.1: {} + + resolve-from@4.0.0: {} + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + semver@7.8.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + slash@3.0.0: {} + + spawn-command@0.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + text-table@0.2.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tree-kill@1.2.2: {} + + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + typescript@5.9.3: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} diff --git a/scripts/build-knowledge-base.js b/scripts/build-knowledge-base.js new file mode 100644 index 0000000000000000000000000000000000000000..b90290316c5802922723014fedf3acab5a2e594d --- /dev/null +++ b/scripts/build-knowledge-base.js @@ -0,0 +1,75 @@ +/** + * Scripts: Build Knowledge Base + * One-time or periodic script to extract n8n node schemas from the n8n repo + * Run: node scripts/build-knowledge-base.js + * This runs externally, NOT inside Cloudflare Worker + */ + +const fs = require('fs'); +const path = require('path'); + +console.log('[KnowledgeBase] Starting n8n node schema extraction...'); +console.log('[KnowledgeBase] This script should be run against a local clone of https://github.com/n8n-io/n8n'); +console.log(''); +console.log('Usage:'); +console.log(' 1. Clone n8n: git clone https://github.com/n8n-io/n8n.git /tmp/n8n'); +console.log(' 2. Run: N8N_REPO=/tmp/n8n node scripts/build-knowledge-base.js'); +console.log(''); + +const N8N_REPO = process.env.N8N_REPO; + +if (!N8N_REPO) { + console.warn('[KnowledgeBase] N8N_REPO env var not set. Using embedded registry from knowledge/node-definitions/'); + console.warn('[KnowledgeBase] For full schema extraction, set N8N_REPO=/path/to/n8n'); + process.exit(0); +} + +const nodesBasePath = path.join(N8N_REPO, 'packages/nodes-base/nodes'); + +if (!fs.existsSync(nodesBasePath)) { + console.error(`[KnowledgeBase] n8n nodes-base not found at: ${nodesBasePath}`); + process.exit(1); +} + +const nodeRegistry = {}; +let count = 0; + +function extractNodeDefs(dir) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + extractNodeDefs(fullPath); + } else if (entry.name.endsWith('.node.ts') || entry.name.endsWith('.node.js')) { + try { + // Read the file to extract description metadata + const content = fs.readFileSync(fullPath, 'utf-8'); + // Extract node type name from file + const typeMatch = content.match(/name:\s*['"]([^'"]+)['"]/); + const versionMatch = content.match(/version:\s*(\d+)/); + const descMatch = content.match(/description:\s*['"]([^'"]+)['"]/); + + if (typeMatch) { + const nodeType = typeMatch[1]; + nodeRegistry[nodeType] = { + type: nodeType, + version: versionMatch ? parseInt(versionMatch[1]) : 1, + description: descMatch ? descMatch[1] : '', + filePath: fullPath.replace(N8N_REPO, ''), + }; + count++; + } + } catch { + // skip files that can't be parsed + } + } + } +} + +extractNodeDefs(nodesBasePath); + +const outputPath = path.join(__dirname, '../knowledge/node-definitions/extracted-registry.json'); +fs.writeFileSync(outputPath, JSON.stringify(nodeRegistry, null, 2)); + +console.log(`[KnowledgeBase] Extracted ${count} node definitions`); +console.log(`[KnowledgeBase] Output written to: ${outputPath}`); diff --git a/scripts/push-upgrade.sh b/scripts/push-upgrade.sh new file mode 100644 index 0000000000000000000000000000000000000000..ae32c23a59c3ffc6d5afc35edbdcda0679077ec7 --- /dev/null +++ b/scripts/push-upgrade.sh @@ -0,0 +1,109 @@ +#!/bin/bash +set -e + +cd /home/user/webapp + +git config user.email "pyaesonegtckglay-dotcom@users.noreply.github.com" +git config user.name "pyaesonegtckglay-dotcom" + +echo "=== Git Status ===" +git status --short + +echo "=== Staging all changes ===" +git add -A + +echo "=== Committing ===" +git commit -m "feat: Workflow Factor OS v2.0 — Full Production Upgrade + +BREAKING CHANGES & MAJOR FEATURES: + +## LLM Gateway (Provider-Agnostic) +- REMOVED: direct OpenAI SDK dependency from all agents +- ADDED: integrations/llm-providers/index.ts — pure fetch-based LLM gateway +- SUPPORTS: openai | anthropic | openai-compatible | custom endpoints +- FEATURES: retry with exponential backoff, fallback provider, timeout handling +- CONFIG: LLM_API_KEY, LLM_GATEWAY_URL, LLM_MODEL, LLM_PROVIDER env vars +- BACKWARD COMPAT: OPENAI_API_KEY still supported as legacy fallback + +## Node Registry System (Strict — No Hallucinated Nodes) +- REBUILT: apps/simulator/src/knowledge/nodeRegistry.ts — 35+ real n8n nodes +- ADDED: NodeRegistryEntry interface with full schema per node type +- ADDED: isValidNodeType(), getNodeDef(), getTriggerNodeTypes() helpers +- ENFORCED: unknown node types REJECTED at graph engine + validator stages +- COVERS: Triggers, AI/LLM, Transform, HTTP, Messaging, Productivity, Database, CRM, Utility + +## Swarm Multi-Agent Orchestration +- NEW: apps/simulator/src/services/swarmOrchestrator.ts +- RUNS: 1-5 parallel workflow design candidates simultaneously +- STRATEGIES: Reliability-First, Simplicity-First, Observability-First, AI-Optimized, Pipeline-Optimized +- SELECTS: best design via quality scoring (validation + registry + expression + dataflow) +- CONFIG: swarmSize option (default: 3) + +## Self-Healing System +- NEW: apps/simulator/src/services/selfHealing.ts +- PHASE 1: Rule-based deterministic fixes (active:false, IDs, positions, retry, onError, unknown nodes) +- PHASE 2: AI-assisted healing for complex expression/logic issues +- MEMORY: records failure patterns, applies historical fixes +- TRIGGERS: automatically on validation failure before re-validation + +## Memory / Learning System +- NEW: apps/simulator/src/services/memorySystem.ts +- STORES: successful patterns, failed patterns, fix strategies, architecture templates +- BUILT-IN: 4 high-performing templates (Telegram Bot, Webhook Pipeline, Data Pipeline, Email-CRM) +- INFLUENCES: planning (memory context injection), architecture selection +- STATS: getStats() for reporting + +## Webhook Auto-Bind System +- NEW: apps/simulator/src/services/webhookAutoBind.ts +- AUTO-DETECTS: trigger type from user request + intent (Telegram, Slack, GitHub, Email, Schedule, Webhook, Manual) +- AUTO-GENERATES: webhook paths, trigger settings, credential stubs +- INJECTS: trigger node into graph automatically +- USER: never needs to manually define triggers + +## Validation Engine (UPGRADED — 7 stages) +- ADDED: Stage F — NodeRegistry validation (rejects unknown/hallucinated node types) +- ADDED: Stage G — DataFlow validation (SET empty, CODE placeholder, IF no conditions) +- UPGRADED: Expression validation (static nodes, placeholder detection, unsafe access) +- SCORE: 7-dimension weighted scoring + +## All Agents Upgraded (Provider-Agnostic) +- intentInterpreter.ts: uses LLMGateway, detects triggerType for auto-bind +- workflowPlanner.ts: uses LLMGateway +- graphEngine.ts: uses LLMGateway + validates all node types against registry +- compiler.ts: uses LLMGateway + strict expression rules + node schema injection +- validator.ts: uses LLMGateway + 7-stage validation +- simulator.ts: uses LLMGateway + chaos testing + deployment blockers +- credentialIntelligence.ts: uses registry for credential mapping + +## All Prompts Upgraded +- intentInterpreter: triggerType detection added +- workflowPlanner: strict node type rules, expression examples, anti-placeholder rules +- graphEngine: registry enforcement, expression requirements, data contract rules +- compiler: full expression guide (correct/wrong examples), node-specific rules +- simulator: scoring formula, chaos scenarios, deployment blockers + +## wrangler.toml (Added/Upgraded) +- LLM_PROVIDER, LLM_GATEWAY_URL, LLM_MODEL vars +- Development environment overrides +- Production environment section +- Secrets documentation updated + +## Worker (Upgraded) +- types/env.ts: LLM_GATEWAY_URL, LLM_MODEL, LLM_PROVIDER, LLM_FALLBACK_* added +- types/workflow.ts: SwarmResultSummary, TriggerDetectionResult, HealingResultSummary types added +- routes/generate.ts: passes LLM config to simulator, stores swarm/healing/trigger results +- routes/health.ts: capabilities info, safety policy documentation +- routes/validate.ts, simulate.ts: llmApiKey support (legacy openaiApiKey still works) + +## Safety Rules (Maintained) +- NEVER auto-activate workflows +- ALWAYS validate + simulate before deploy +- ALWAYS require human approval before activation +- NEVER deploy partially valid workflows +- NEVER allow unknown node types +- active: false enforced at compiler + self-healing stages" + +echo "=== Pushing to main ===" +git push origin main + +echo "=== Done! ===" diff --git a/scripts/validate-env.js b/scripts/validate-env.js new file mode 100644 index 0000000000000000000000000000000000000000..86ce01a6f8130ab42424d808d98449a4e3369d27 --- /dev/null +++ b/scripts/validate-env.js @@ -0,0 +1,42 @@ +/** + * Scripts: Validate Environment + * Checks all required env vars before deployment + */ + +const REQUIRED_SIMULATOR_VARS = [ + 'OPENAI_API_KEY', + 'N8N_BASE_URL', + 'N8N_API_KEY', + 'INTERNAL_API_SECRET', +]; + +const REQUIRED_WORKER_SECRETS = [ + 'OPENAI_API_KEY', + 'N8N_API_KEY', + 'INTERNAL_API_SECRET', +]; + +let hasError = false; + +console.log('[ValidateEnv] Checking Simulator service environment variables...'); +for (const key of REQUIRED_SIMULATOR_VARS) { + if (!process.env[key]) { + console.error(` ✗ Missing: ${key}`); + hasError = true; + } else { + console.log(` ✓ ${key}`); + } +} + +console.log(''); +console.log('[ValidateEnv] Cloudflare Worker secrets (set via wrangler secret put):'); +for (const key of REQUIRED_WORKER_SECRETS) { + console.log(` → wrangler secret put ${key}`); +} + +if (hasError) { + console.error('\n[ValidateEnv] ✗ Environment validation failed. Set missing variables before deploying.'); + process.exit(1); +} else { + console.log('\n[ValidateEnv] ✓ All environment variables present.'); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..624e5409adc1ec66399b4febe9b305f473152b36 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "exactOptionalPropertyTypes": false, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./", + "paths": { + "@wfo/core/*": ["./core/*"], + "@wfo/integrations/*": ["./integrations/*"], + "@wfo/validation/*": ["./validation/*"], + "@wfo/observability/*": ["./observability/*"], + "@wfo/knowledge/*": ["./knowledge/*"], + "@wfo/config/*": ["./config/*"] + } + }, + "include": ["apps/**/*", "core/**/*", "integrations/**/*", "validation/**/*", "observability/**/*", "knowledge/**/*", "config/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/validation/expressions/index.ts b/validation/expressions/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f81e45e2cf7172fb8f71e18a3cf625da911788e4 --- /dev/null +++ b/validation/expressions/index.ts @@ -0,0 +1,58 @@ +/** + * Expression Validation Utilities + * Detects unsafe n8n expressions before deployment + */ + +export interface ExpressionIssue { + expression: string; + nodeId: string; + issue: string; + fix: string; +} + +const UNSAFE_PATTERNS = [ + { + pattern: /\{\{\s*\$json\.[a-zA-Z_][a-zA-Z0-9_.]+\s*\}\}/, + issue: 'Direct property chain without optional chaining - may throw TypeError on null', + fix: 'Use optional chaining: {{ $json?.a?.b?.c ?? defaultValue }}', + }, + { + pattern: /\{\{\s*\$node\["[^"]+"\]\.json\.[a-zA-Z]/, + issue: 'Node reference without null guard - node may not exist in execution path', + fix: 'Use: {{ $node["NodeName"].json?.field ?? "" }}', + }, + { + pattern: /\{\{\s*\$items\(\)[^}]*\[0\]/, + issue: 'Direct array access [0] without length check - may be empty array', + fix: 'Use: {{ $items()?.[0]?.json?.field ?? "" }}', + }, +]; + +export function analyzeExpressions( + value: unknown, + nodeId: string, + path = '', +): ExpressionIssue[] { + const issues: ExpressionIssue[] = []; + + if (typeof value === 'string') { + for (const { pattern, issue, fix } of UNSAFE_PATTERNS) { + if (pattern.test(value)) { + issues.push({ expression: value, nodeId, issue: `${path}: ${issue}`, fix }); + } + } + } else if (typeof value === 'object' && value !== null) { + for (const [key, val] of Object.entries(value)) { + issues.push(...analyzeExpressions(val, nodeId, `${path}.${key}`)); + } + } + + return issues; +} + +export function sanitizeExpression(expr: string): string { + // Auto-fix common unsafe patterns + return expr + .replace(/\$json\.([a-zA-Z])/g, '$json?.$1') + .replace(/\$node\["([^"]+)"\]\.json\.([a-zA-Z])/g, '$node["$1"].json?.$2'); +} diff --git a/validation/reliability/index.ts b/validation/reliability/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1581e10b3d655a70b795e1bda27a2083a01a86fc --- /dev/null +++ b/validation/reliability/index.ts @@ -0,0 +1,70 @@ +/** + * Reliability Validation Utilities + * Checks for retry policies, fallbacks, monitoring presence + */ +import type { WorkflowGraph, N8nWorkflow } from '../../apps/worker/src/types/workflow'; + +export interface ReliabilityCheckResult { + passed: boolean; + score: number; + findings: string[]; + recommendations: string[]; +} + +export function checkReliability(workflow: N8nWorkflow, graph: WorkflowGraph): ReliabilityCheckResult { + const findings: string[] = []; + const recommendations: string[] = []; + let deductions = 0; + + // Check retry on external API nodes + const externalNodes = workflow.nodes.filter((n) => + n.type.includes('httpRequest') || + n.type.includes('openAi') || + n.type.includes('telegram') || + n.type.includes('slack') || + n.type.includes('gmail'), + ); + + externalNodes.forEach((node) => { + if (!node.retryOnFail) { + findings.push(`Node "${node.name}" calls external service without retry policy`); + recommendations.push(`Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 to "${node.name}"`); + deductions += 10; + } + }); + + // Check for error workflow + if (!workflow.settings?.errorWorkflow) { + findings.push('No error workflow configured for global error handling'); + recommendations.push('Set settings.errorWorkflow to an alert/notification workflow ID'); + deductions += 8; + } + + // Check for monitoring nodes + const monitoringNodes = graph.nodes.filter((n) => n.layer === 'monitoring'); + if (monitoringNodes.length === 0) { + findings.push('No monitoring layer nodes detected in workflow'); + recommendations.push('Add at least one structured logging or notification node'); + deductions += 10; + } + + // Check for fallback nodes on critical nodes + const criticalWithoutFallback = graph.nodes.filter( + (n) => n.isCritical && !n.fallbackNodeId, + ); + if (criticalWithoutFallback.length > 0) { + criticalWithoutFallback.forEach((n) => { + findings.push(`Critical node "${n.label}" has no fallback path`); + recommendations.push(`Add fallbackNodeId to "${n.label}" pointing to an error handler`); + deductions += 8; + }); + } + + const score = Math.max(0, 100 - deductions); + return { + passed: deductions < 30, + score, + findings, + recommendations, + }; +} diff --git a/validation/schema/index.ts b/validation/schema/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..346d152407f96953b3aeda57b127bdead8dd57b6 --- /dev/null +++ b/validation/schema/index.ts @@ -0,0 +1,90 @@ +/** + * Validation Schema Definitions (Zod) + * Shared validation schemas used across Worker and Simulator + */ +// @ts-ignore +import { z } from 'zod'; + +export const WorkflowIntentSchema = z.object({ + workflowType: z.string(), + requiresAI: z.boolean(), + integrations: z.array(z.string()), + riskLevel: z.enum(['low', 'medium', 'high', 'critical']), + requiresHumanApproval: z.boolean(), + syncVsAsync: z.enum(['sync', 'async', 'hybrid']), + scalingRequirements: z.enum(['low', 'medium', 'high']), + identifiedRisks: z.array(z.string()), + estimatedComplexity: z.enum(['simple', 'moderate', 'complex']), + domain: z.enum([ + 'sales_automation', 'ai_workflow', 'data_pipeline', 'webhook_system', + 'notification', 'crm_integration', 'devops', 'ecommerce', 'general', + ]), +}); + +export const RetryPolicySchema = z.object({ + maxRetries: z.number().int().min(1).max(10), + backoffStrategy: z.enum(['linear', 'exponential']), + initialDelayMs: z.number().min(100).max(60000), + maxDelayMs: z.number().min(1000).max(300000), + retryOnStatusCodes: z.array(z.number()).optional(), +}); + +export const GraphNodeSchema = z.object({ + id: z.string().uuid(), + label: z.string().min(1), + type: z.string(), + n8nNodeType: z.string(), + parameters: z.record(z.unknown()), + credentials: z.record(z.string()).optional(), + retryPolicy: RetryPolicySchema.optional(), + position: z.object({ x: z.number(), y: z.number() }), + layer: z.enum([ + 'trigger', 'validation', 'queue', 'processing', + 'ai_inference', 'decision', 'persistence', + 'retry_fallback', 'monitoring', 'human_escalation', + ]), + isAsync: z.boolean(), + isCritical: z.boolean(), + fallbackNodeId: z.string().nullable().optional(), +}); + +export const N8nNodeSchema = z.object({ + id: z.string(), + name: z.string().min(1), + type: z.string(), + typeVersion: z.number().int().min(1), + position: z.tuple([z.number(), z.number()]), + parameters: z.record(z.unknown()), + credentials: z.record(z.object({ id: z.string(), name: z.string() })).optional(), + onError: z.enum(['stopWorkflow', 'continueRegularOutput', 'continueErrorOutput']).optional(), + retryOnFail: z.boolean().optional(), + maxTries: z.number().int().min(1).max(10).optional(), + waitBetweenTries: z.number().min(100).optional(), + notes: z.string().optional(), +}); + +export const N8nWorkflowSchema = z.object({ + id: z.string().optional(), + name: z.string().min(1), + nodes: z.array(N8nNodeSchema).min(1), + connections: z.record(z.unknown()), + active: z.literal(false), // enforce: compiled workflows are never active + settings: z.object({ + executionOrder: z.enum(['v0', 'v1']).optional(), + saveManualExecutions: z.boolean().optional(), + callerPolicy: z.string().optional(), + errorWorkflow: z.string().optional(), + timezone: z.string().optional(), + }).optional(), + tags: z.array(z.string()).optional(), + meta: z.record(z.unknown()).optional(), +}); + +export const ValidationIssueSchema = z.object({ + id: z.string(), + severity: z.enum(['error', 'warning', 'info']), + category: z.string(), + nodeId: z.string().optional(), + message: z.string(), + suggestion: z.string(), +});