From 1b082a3eb684e403c78b5d754df089b11f490a52 Mon Sep 17 00:00:00 2001 From: DaKheera47 Date: Thu, 11 Dec 2025 22:31:59 +0000 Subject: [PATCH] orchestrator initial commit... --- job-extractor/src/main.ts | 4 +- orchestrator/.env.example | 21 + orchestrator/.gitignore | 26 + orchestrator/README.md | 123 + orchestrator/index.html | 17 + orchestrator/package-lock.json | 5403 +++++++++++++++++ orchestrator/package.json | 43 + orchestrator/src/client/App.tsx | 162 + orchestrator/src/client/api/client.ts | 91 + orchestrator/src/client/api/index.ts | 1 + orchestrator/src/client/components/Header.tsx | 64 + orchestrator/src/client/components/Icons.tsx | 118 + .../src/client/components/JobCard.tsx | 174 + .../src/client/components/JobList.tsx | 90 + .../src/client/components/ScoreIndicator.tsx | 37 + orchestrator/src/client/components/Stats.tsx | 50 + .../src/client/components/StatusBadge.tsx | 28 + orchestrator/src/client/components/Toast.tsx | 44 + orchestrator/src/client/components/index.ts | 8 + orchestrator/src/client/main.tsx | 10 + orchestrator/src/client/styles/index.css | 680 +++ orchestrator/src/server/api/index.ts | 1 + orchestrator/src/server/api/routes.ts | 272 + orchestrator/src/server/db/index.ts | 30 + orchestrator/src/server/db/migrate.ts | 77 + orchestrator/src/server/db/schema.ts | 58 + orchestrator/src/server/index.ts | 71 + orchestrator/src/server/pipeline/index.ts | 1 + .../src/server/pipeline/orchestrator.ts | 283 + orchestrator/src/server/pipeline/run.ts | 45 + orchestrator/src/server/repositories/index.ts | 2 + orchestrator/src/server/repositories/jobs.ts | 190 + .../src/server/repositories/pipeline.ts | 94 + orchestrator/src/server/services/crawler.ts | 112 + orchestrator/src/server/services/index.ts | 5 + orchestrator/src/server/services/notion.ts | 89 + orchestrator/src/server/services/pdf.ts | 144 + orchestrator/src/server/services/scorer.ts | 143 + orchestrator/src/server/services/summary.ts | 153 + orchestrator/src/shared/index.ts | 1 + orchestrator/src/shared/types.ts | 106 + orchestrator/tsconfig.json | 22 + orchestrator/tsconfig.server.json | 10 + orchestrator/vite.config.ts | 27 + resume-generator/rxresume_automation.py | 6 +- ..._b551b26e-7cf0-4bc5-be69-36d8e813f5b2.json | 661 ++ 46 files changed, 9792 insertions(+), 5 deletions(-) create mode 100644 orchestrator/.env.example create mode 100644 orchestrator/.gitignore create mode 100644 orchestrator/README.md create mode 100644 orchestrator/index.html create mode 100644 orchestrator/package-lock.json create mode 100644 orchestrator/package.json create mode 100644 orchestrator/src/client/App.tsx create mode 100644 orchestrator/src/client/api/client.ts create mode 100644 orchestrator/src/client/api/index.ts create mode 100644 orchestrator/src/client/components/Header.tsx create mode 100644 orchestrator/src/client/components/Icons.tsx create mode 100644 orchestrator/src/client/components/JobCard.tsx create mode 100644 orchestrator/src/client/components/JobList.tsx create mode 100644 orchestrator/src/client/components/ScoreIndicator.tsx create mode 100644 orchestrator/src/client/components/Stats.tsx create mode 100644 orchestrator/src/client/components/StatusBadge.tsx create mode 100644 orchestrator/src/client/components/Toast.tsx create mode 100644 orchestrator/src/client/components/index.ts create mode 100644 orchestrator/src/client/main.tsx create mode 100644 orchestrator/src/client/styles/index.css create mode 100644 orchestrator/src/server/api/index.ts create mode 100644 orchestrator/src/server/api/routes.ts create mode 100644 orchestrator/src/server/db/index.ts create mode 100644 orchestrator/src/server/db/migrate.ts create mode 100644 orchestrator/src/server/db/schema.ts create mode 100644 orchestrator/src/server/index.ts create mode 100644 orchestrator/src/server/pipeline/index.ts create mode 100644 orchestrator/src/server/pipeline/orchestrator.ts create mode 100644 orchestrator/src/server/pipeline/run.ts create mode 100644 orchestrator/src/server/repositories/index.ts create mode 100644 orchestrator/src/server/repositories/jobs.ts create mode 100644 orchestrator/src/server/repositories/pipeline.ts create mode 100644 orchestrator/src/server/services/crawler.ts create mode 100644 orchestrator/src/server/services/index.ts create mode 100644 orchestrator/src/server/services/notion.ts create mode 100644 orchestrator/src/server/services/pdf.ts create mode 100644 orchestrator/src/server/services/scorer.ts create mode 100644 orchestrator/src/server/services/summary.ts create mode 100644 orchestrator/src/shared/index.ts create mode 100644 orchestrator/src/shared/types.ts create mode 100644 orchestrator/tsconfig.json create mode 100644 orchestrator/tsconfig.server.json create mode 100644 orchestrator/vite.config.ts create mode 100644 resume-generator/temp_resume_b551b26e-7cf0-4bc5-be69-36d8e813f5b2.json diff --git a/job-extractor/src/main.ts b/job-extractor/src/main.ts index 8dcdf5d..25d1a3c 100644 --- a/job-extractor/src/main.ts +++ b/job-extractor/src/main.ts @@ -19,7 +19,7 @@ const crawler = new PlaywrightCrawler({ maxRequestsPerCrawl: 20, // Add delay between requests to slow down the process minConcurrency: 1, - maxConcurrency: 2, + maxConcurrency: 5, navigationTimeoutSecs: 60, // Add delay between requests (in milliseconds) // requestHandlerTimeoutSecs: 50, @@ -30,7 +30,7 @@ const crawler = new PlaywrightCrawler({ launchContext: { launcher: firefox, launchOptions: await launchOptions({ - headless: false, + headless: true, // block_images: true, // Pass your own Camoufox parameters here... // block_images: true, diff --git a/orchestrator/.env.example b/orchestrator/.env.example new file mode 100644 index 0000000..fc94358 --- /dev/null +++ b/orchestrator/.env.example @@ -0,0 +1,21 @@ +# Server +PORT=3001 + +# OpenRouter API (for AI features) +OPENROUTER_API_KEY=your_openrouter_api_key_here +MODEL=openai/gpt-4o-mini + +# Notion integration (optional) +NOTION_API_KEY= +NOTION_DATABASE_ID= + +# Webhook security (optional) +WEBHOOK_SECRET= + +# Pipeline configuration +PIPELINE_TOP_N=10 +PIPELINE_MIN_SCORE=50 + +# RXResume credentials (for PDF generation) +RXRESUME_EMAIL= +RXRESUME_PASSWORD= diff --git a/orchestrator/.gitignore b/orchestrator/.gitignore new file mode 100644 index 0000000..bfdfb16 --- /dev/null +++ b/orchestrator/.gitignore @@ -0,0 +1,26 @@ +# Dependencies +node_modules/ + +# Build output +dist/ + +# Data (local database and generated files) +data/ + +# Environment +.env +.env.local + +# OS files +.DS_Store +Thumbs.db + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Logs +*.log +npm-debug.log* diff --git a/orchestrator/README.md b/orchestrator/README.md new file mode 100644 index 0000000..104264e --- /dev/null +++ b/orchestrator/README.md @@ -0,0 +1,123 @@ +# Job Ops Orchestrator + +A unified orchestrator for the job application pipeline. Discovers jobs, scores them for suitability, generates tailored resumes, and provides a UI to manage applications. + +## Architecture + +``` +orchestrator/ +├── src/ +│ ├── server/ # Express backend +│ │ ├── api/ # REST API routes +│ │ ├── db/ # SQLite + Drizzle ORM +│ │ ├── pipeline/ # Orchestration logic +│ │ ├── repositories/ # Data access layer +│ │ └── services/ # Integrations (crawler, AI, PDF) +│ ├── client/ # React frontend +│ │ ├── api/ # API client +│ │ ├── components/ # UI components +│ │ └── styles/ # CSS design system +│ └── shared/ # Shared types +├── data/ # SQLite DB + generated PDFs (gitignored) +└── public/ # Static assets +``` + +## Setup + +1. **Install dependencies:** + ```bash + cd orchestrator + npm install + ``` + +2. **Set up environment:** + ```bash + cp .env.example .env + # Edit .env with your API keys + ``` + +3. **Initialize database:** + ```bash + npm run db:migrate + ``` + +4. **Start development server:** + ```bash + npm run dev + ``` + + This starts: + - Backend API at `http://localhost:3001` + - Frontend at `http://localhost:5173` + +## API Endpoints + +### Jobs + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/jobs` | List all jobs (filter with `?status=ready,discovered`) | +| GET | `/api/jobs/:id` | Get single job | +| PATCH | `/api/jobs/:id` | Update job | +| POST | `/api/jobs/:id/process` | Generate resume for job | +| POST | `/api/jobs/:id/apply` | Mark as applied + sync to Notion | +| POST | `/api/jobs/:id/reject` | Mark as rejected | + +### Pipeline + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/pipeline/status` | Get pipeline status | +| GET | `/api/pipeline/runs` | Get recent pipeline runs | +| POST | `/api/pipeline/run` | Trigger pipeline manually | +| POST | `/api/webhook/trigger` | Webhook for n8n (use `WEBHOOK_SECRET`) | + +## Daily Flow + +1. **17:00 - n8n triggers pipeline:** + - Calls `POST /api/webhook/trigger` + - Pipeline crawls Gradcracker + - Scores jobs with AI + - Generates tailored resumes for top 10 + +2. **You review in the UI:** + - See jobs at `http://localhost:5173` + - "Ready" tab shows jobs with generated PDFs + - Click "View Job" to open application + - Download PDF and apply manually + - Click "Mark Applied" → syncs to Notion + +## n8n Setup + +Create a workflow with: + +1. **Schedule Trigger** - Every day at 17:00 +2. **HTTP Request:** + - Method: POST + - URL: `http://localhost:3001/api/webhook/trigger` + - Headers: `Authorization: Bearer YOUR_WEBHOOK_SECRET` + +## Development + +```bash +# Run just the server +npm run dev:server + +# Run just the client +npm run dev:client + +# Run the pipeline manually +npm run pipeline:run + +# Build for production +npm run build +npm start +``` + +## Tech Stack + +- **Backend:** Express, TypeScript, Drizzle ORM, SQLite +- **Frontend:** React, Vite, CSS (custom design system) +- **AI:** OpenRouter API (GPT-4o-mini) +- **PDF Generation:** Wraps existing Python RXResume automation +- **Job Crawling:** Wraps existing TypeScript Crawlee crawler diff --git a/orchestrator/index.html b/orchestrator/index.html new file mode 100644 index 0000000..409f757 --- /dev/null +++ b/orchestrator/index.html @@ -0,0 +1,17 @@ + + + + + + + + Job Ops | Orchestrator + + + + + +
+ + + diff --git a/orchestrator/package-lock.json b/orchestrator/package-lock.json new file mode 100644 index 0000000..3140b91 --- /dev/null +++ b/orchestrator/package-lock.json @@ -0,0 +1,5403 @@ +{ + "name": "job-ops-orchestrator", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "job-ops-orchestrator", + "version": "1.0.0", + "dependencies": { + "better-sqlite3": "^11.6.0", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "drizzle-orm": "^0.38.2", + "express": "^4.18.2", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.8", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^22.10.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "concurrently": "^9.1.0", + "drizzle-kit": "^0.30.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.0.2", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "vite": "^6.0.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@petamoriken/float16": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz", + "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/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.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "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/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": "22.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", + "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "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/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/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/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "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/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/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/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/baseline-browser-mapping": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz", + "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "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.14.0", + "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/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "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/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "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-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/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "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": ">=18" + }, + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/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.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/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/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/drizzle-kit": { + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.6.tgz", + "integrity": "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.19.7", + "esbuild-register": "^3.5.0", + "gel": "^2.0.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.38.4", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.38.4.tgz", + "integrity": "sha512-s7/5BpLKO+WJRHspvpqTydxFob8i1vo2rEx4pY6TGY7QSMuUfWUuzaY0DIpXCkgHOo37BaFC+SJQb99dDUXT3Q==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/react": ">=18", + "@types/sql.js": "*", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "react": ">=18", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "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/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "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/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "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.14.0", + "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/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/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "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/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-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "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/gel": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gel/-/gel-2.2.0.tgz", + "integrity": "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@petamoriken/float16": "^3.8.7", + "debug": "^4.3.4", + "env-paths": "^3.0.0", + "semver": "^7.6.2", + "shell-quote": "^1.8.1", + "which": "^4.0.0" + }, + "bin": { + "gel": "dist/cli.mjs" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/gel/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-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-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "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/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.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/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/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "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-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/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/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/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.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/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "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/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "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-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/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-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz", + "integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz", + "integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-router": "7.10.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/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-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/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", + "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", + "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.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "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/send/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/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/serve-static/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/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" + }, + "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/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.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "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-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "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": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^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/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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/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/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/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "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/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/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/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/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/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/orchestrator/package.json b/orchestrator/package.json new file mode 100644 index 0000000..01b7450 --- /dev/null +++ b/orchestrator/package.json @@ -0,0 +1,43 @@ +{ + "name": "job-ops-orchestrator", + "version": "1.0.0", + "type": "module", + "description": "Unified orchestrator for job application pipeline", + "main": "dist/server/index.js", + "scripts": { + "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"", + "dev:server": "tsx watch src/server/index.ts", + "dev:client": "vite", + "build": "npm run build:client && npm run build:server", + "build:server": "tsc -p tsconfig.server.json", + "build:client": "vite build", + "start": "node dist/server/index.js", + "db:migrate": "tsx src/server/db/migrate.ts", + "pipeline:run": "tsx src/server/pipeline/run.ts" + }, + "dependencies": { + "better-sqlite3": "^11.6.0", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "drizzle-orm": "^0.38.2", + "express": "^4.18.2", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.8", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^22.10.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "concurrently": "^9.1.0", + "drizzle-kit": "^0.30.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.0.2", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "vite": "^6.0.3" + } +} diff --git a/orchestrator/src/client/App.tsx b/orchestrator/src/client/App.tsx new file mode 100644 index 0000000..d39096a --- /dev/null +++ b/orchestrator/src/client/App.tsx @@ -0,0 +1,162 @@ +/** + * Main App component. + */ + +import React, { useState, useEffect, useCallback } from 'react'; +import type { Job, JobStatus } from '../shared/types'; +import { Header, Stats, JobList, ToastContainer, Toast } from './components'; +import * as api from './api'; + +export const App: React.FC = () => { + // State + const [jobs, setJobs] = useState([]); + const [stats, setStats] = useState>({ + discovered: 0, + processing: 0, + ready: 0, + applied: 0, + rejected: 0, + expired: 0, + }); + const [isLoading, setIsLoading] = useState(true); + const [isPipelineRunning, setIsPipelineRunning] = useState(false); + const [processingJobId, setProcessingJobId] = useState(null); + const [toasts, setToasts] = useState([]); + + // Toast helpers + const addToast = useCallback((message: string, type: Toast['type']) => { + const id = Math.random().toString(36).slice(2); + setToasts(prev => [...prev, { id, message, type }]); + }, []); + + const dismissToast = useCallback((id: string) => { + setToasts(prev => prev.filter(t => t.id !== id)); + }, []); + + // Load jobs + const loadJobs = useCallback(async () => { + try { + setIsLoading(true); + const data = await api.getJobs(); + setJobs(data.jobs); + setStats(data.byStatus); + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to load jobs'; + addToast(message, 'error'); + } finally { + setIsLoading(false); + } + }, [addToast]); + + // Check pipeline status + const checkPipelineStatus = useCallback(async () => { + try { + const status = await api.getPipelineStatus(); + setIsPipelineRunning(status.isRunning); + } catch { + // Ignore errors + } + }, []); + + // Initial load + useEffect(() => { + loadJobs(); + checkPipelineStatus(); + + // Poll for updates + const interval = setInterval(() => { + loadJobs(); + checkPipelineStatus(); + }, 10000); + + return () => clearInterval(interval); + }, [loadJobs, checkPipelineStatus]); + + // Run pipeline + const handleRunPipeline = async () => { + try { + setIsPipelineRunning(true); + await api.runPipeline(); + addToast('Pipeline started! This may take a few minutes.', 'info'); + + // Poll more frequently while running + const pollInterval = setInterval(async () => { + const status = await api.getPipelineStatus(); + if (!status.isRunning) { + clearInterval(pollInterval); + setIsPipelineRunning(false); + loadJobs(); + addToast('Pipeline completed!', 'success'); + } + }, 5000); + } catch (error) { + setIsPipelineRunning(false); + const message = error instanceof Error ? error.message : 'Failed to start pipeline'; + addToast(message, 'error'); + } + }; + + // Process single job + const handleProcess = async (jobId: string) => { + try { + setProcessingJobId(jobId); + await api.processJob(jobId); + addToast('Resume generated successfully!', 'success'); + loadJobs(); + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to process job'; + addToast(message, 'error'); + } finally { + setProcessingJobId(null); + } + }; + + // Mark as applied + const handleApply = async (jobId: string) => { + try { + await api.markAsApplied(jobId); + addToast('Marked as applied! ✅', 'success'); + loadJobs(); + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to mark as applied'; + addToast(message, 'error'); + } + }; + + // Reject job + const handleReject = async (jobId: string) => { + try { + await api.rejectJob(jobId); + addToast('Job skipped', 'info'); + loadJobs(); + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to reject job'; + addToast(message, 'error'); + } + }; + + return ( + <> +
+ +
+ + + +
+ + + + ); +}; diff --git a/orchestrator/src/client/api/client.ts b/orchestrator/src/client/api/client.ts new file mode 100644 index 0000000..0d470e3 --- /dev/null +++ b/orchestrator/src/client/api/client.ts @@ -0,0 +1,91 @@ +/** + * API client for the orchestrator backend. + */ + +import type { + Job, + ApiResponse, + JobsListResponse, + PipelineStatusResponse, + PipelineRun +} from '../../shared/types'; + +const API_BASE = '/api'; + +async function fetchApi( + endpoint: string, + options?: RequestInit +): Promise { + const response = await fetch(`${API_BASE}${endpoint}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + }); + + const data: ApiResponse = await response.json(); + + if (!data.success) { + throw new Error(data.error || 'API request failed'); + } + + return data.data as T; +} + +// Jobs API +export async function getJobs(statuses?: string[]): Promise { + const query = statuses?.length ? `?status=${statuses.join(',')}` : ''; + return fetchApi(`/jobs${query}`); +} + +export async function getJob(id: string): Promise { + return fetchApi(`/jobs/${id}`); +} + +export async function updateJob( + id: string, + update: Partial +): Promise { + return fetchApi(`/jobs/${id}`, { + method: 'PATCH', + body: JSON.stringify(update), + }); +} + +export async function processJob(id: string): Promise { + return fetchApi(`/jobs/${id}/process`, { + method: 'POST', + }); +} + +export async function markAsApplied(id: string): Promise { + return fetchApi(`/jobs/${id}/apply`, { + method: 'POST', + }); +} + +export async function rejectJob(id: string): Promise { + return fetchApi(`/jobs/${id}/reject`, { + method: 'POST', + }); +} + +// Pipeline API +export async function getPipelineStatus(): Promise { + return fetchApi('/pipeline/status'); +} + +export async function getPipelineRuns(): Promise { + return fetchApi('/pipeline/runs'); +} + +export async function runPipeline(config?: { + topN?: number; + minSuitabilityScore?: number; +}): Promise<{ message: string }> { + return fetchApi<{ message: string }>('/pipeline/run', { + method: 'POST', + body: JSON.stringify(config || {}), + }); +} diff --git a/orchestrator/src/client/api/index.ts b/orchestrator/src/client/api/index.ts new file mode 100644 index 0000000..4f1cce4 --- /dev/null +++ b/orchestrator/src/client/api/index.ts @@ -0,0 +1 @@ +export * from './client'; diff --git a/orchestrator/src/client/components/Header.tsx b/orchestrator/src/client/components/Header.tsx new file mode 100644 index 0000000..9890504 --- /dev/null +++ b/orchestrator/src/client/components/Header.tsx @@ -0,0 +1,64 @@ +/** + * Header component with logo and pipeline trigger. + */ + +import React from 'react'; +import { RocketIcon, PlayIcon, RefreshIcon } from './Icons'; + +interface HeaderProps { + onRunPipeline: () => void; + onRefresh: () => void; + isPipelineRunning: boolean; + isLoading: boolean; +} + +export const Header: React.FC = ({ + onRunPipeline, + onRefresh, + isPipelineRunning, + isLoading, +}) => { + return ( +
+
+
+
+
+ +
+ Job Ops +
+ +
+ + + +
+
+
+
+ ); +}; diff --git a/orchestrator/src/client/components/Icons.tsx b/orchestrator/src/client/components/Icons.tsx new file mode 100644 index 0000000..24d33ce --- /dev/null +++ b/orchestrator/src/client/components/Icons.tsx @@ -0,0 +1,118 @@ +/** + * SVG Icons as React components. + */ + +import React from 'react'; + +interface IconProps { + className?: string; + size?: number; +} + +export const BriefcaseIcon: React.FC = ({ className, size = 16 }) => ( + + + + +); + +export const MapPinIcon: React.FC = ({ className, size = 16 }) => ( + + + + +); + +export const CalendarIcon: React.FC = ({ className, size = 16 }) => ( + + + + + + +); + +export const DollarIcon: React.FC = ({ className, size = 16 }) => ( + + + + +); + +export const GraduationCapIcon: React.FC = ({ className, size = 16 }) => ( + + + + +); + +export const ExternalLinkIcon: React.FC = ({ className, size = 16 }) => ( + + + + + +); + +export const FileTextIcon: React.FC = ({ className, size = 16 }) => ( + + + + + + + +); + +export const CheckCircleIcon: React.FC = ({ className, size = 16 }) => ( + + + + +); + +export const XCircleIcon: React.FC = ({ className, size = 16 }) => ( + + + + + +); + +export const RefreshIcon: React.FC = ({ className, size = 16 }) => ( + + + + + +); + +export const PlayIcon: React.FC = ({ className, size = 16 }) => ( + + + +); + +export const DownloadIcon: React.FC = ({ className, size = 16 }) => ( + + + + + +); + +export const XIcon: React.FC = ({ className, size = 16 }) => ( + + + + +); + +export const RocketIcon: React.FC = ({ className, size = 16 }) => ( + + + + + + +); diff --git a/orchestrator/src/client/components/JobCard.tsx b/orchestrator/src/client/components/JobCard.tsx new file mode 100644 index 0000000..1efb4cc --- /dev/null +++ b/orchestrator/src/client/components/JobCard.tsx @@ -0,0 +1,174 @@ +/** + * Individual job card component. + */ + +import React from 'react'; +import type { Job } from '../../shared/types'; +import { StatusBadge } from './StatusBadge'; +import { ScoreIndicator } from './ScoreIndicator'; +import { + MapPinIcon, + CalendarIcon, + DollarIcon, + GraduationCapIcon, + ExternalLinkIcon, + DownloadIcon, + CheckCircleIcon, + XCircleIcon, + RefreshIcon, +} from './Icons'; + +interface JobCardProps { + job: Job; + onApply: (id: string) => void; + onReject: (id: string) => void; + onProcess: (id: string) => void; + isProcessing: boolean; +} + +export const JobCard: React.FC = ({ + job, + onApply, + onReject, + onProcess, + isProcessing, +}) => { + const formatDate = (dateStr: string | null) => { + if (!dateStr) return null; + try { + return new Date(dateStr).toLocaleDateString('en-GB', { + day: 'numeric', + month: 'short', + year: 'numeric', + }); + } catch { + return dateStr; + } + }; + + const hasPdf = !!job.pdfPath; + const canApply = job.status === 'ready'; + const canProcess = job.status === 'discovered'; + const canReject = ['discovered', 'ready'].includes(job.status); + + return ( +
+
+
+

{job.title}

+

{job.employer}

+
+
+ + +
+
+ +
+ {job.location && ( + + + {job.location} + + )} + {job.deadline && ( + + + {job.deadline} + + )} + {job.salary && ( + + + {job.salary} + + )} + {job.degreeRequired && ( + + + {job.degreeRequired} + + )} +
+ + {job.suitabilityReason && ( +

+ "{job.suitabilityReason}" +

+ )} + +
+ {/* View job posting */} + + + View Job + + + {/* Download PDF */} + {hasPdf && ( + + + Download PDF + + )} + + {/* Process job */} + {canProcess && ( + + )} + + {/* Reject */} + {canReject && ( + + )} + + {/* Mark as applied */} + {canApply && ( + + )} +
+
+ ); +}; diff --git a/orchestrator/src/client/components/JobList.tsx b/orchestrator/src/client/components/JobList.tsx new file mode 100644 index 0000000..2583d19 --- /dev/null +++ b/orchestrator/src/client/components/JobList.tsx @@ -0,0 +1,90 @@ +/** + * Job list with filtering tabs. + */ + +import React, { useState } from 'react'; +import type { Job, JobStatus } from '../../shared/types'; +import { JobCard } from './JobCard'; + +interface JobListProps { + jobs: Job[]; + onApply: (id: string) => void; + onReject: (id: string) => void; + onProcess: (id: string) => void; + processingJobId: string | null; +} + +type FilterTab = 'ready' | 'discovered' | 'applied' | 'all'; + +const tabs: Array<{ id: FilterTab; label: string; statuses: JobStatus[] }> = [ + { id: 'ready', label: '✨ Ready to Apply', statuses: ['ready'] }, + { id: 'discovered', label: '🔍 Discovered', statuses: ['discovered', 'processing'] }, + { id: 'applied', label: '✅ Applied', statuses: ['applied'] }, + { id: 'all', label: '📋 All Jobs', statuses: [] }, +]; + +export const JobList: React.FC = ({ + jobs, + onApply, + onReject, + onProcess, + processingJobId, +}) => { + const [activeTab, setActiveTab] = useState('ready'); + + const filteredJobs = React.useMemo(() => { + const tab = tabs.find(t => t.id === activeTab); + if (!tab || tab.statuses.length === 0) { + return jobs; + } + return jobs.filter(job => tab.statuses.includes(job.status)); + }, [jobs, activeTab]); + + return ( +
+
+ {tabs.map(tab => { + const count = tab.statuses.length === 0 + ? jobs.length + : jobs.filter(j => tab.statuses.includes(j.status)).length; + + return ( + + ); + })} +
+ + {filteredJobs.length === 0 ? ( +
+
📭
+

No jobs found

+

+ {activeTab === 'ready' && 'Run the pipeline to discover and process new jobs.'} + {activeTab === 'discovered' && 'All discovered jobs have been processed.'} + {activeTab === 'applied' && "You haven't applied to any jobs yet."} + {activeTab === 'all' && 'No jobs in the system yet. Run the pipeline to get started!'} +

+
+ ) : ( +
+ {filteredJobs.map(job => ( + + ))} +
+ )} +
+ ); +}; diff --git a/orchestrator/src/client/components/ScoreIndicator.tsx b/orchestrator/src/client/components/ScoreIndicator.tsx new file mode 100644 index 0000000..0d7ab54 --- /dev/null +++ b/orchestrator/src/client/components/ScoreIndicator.tsx @@ -0,0 +1,37 @@ +/** + * Suitability score display component. + */ + +import React from 'react'; + +interface ScoreIndicatorProps { + score: number | null; +} + +export const ScoreIndicator: React.FC = ({ score }) => { + if (score === null) { + return ( + + Not scored + + ); + } + + const getScoreClass = () => { + if (score >= 70) return 'score-high'; + if (score >= 40) return 'score-medium'; + return 'score-low'; + }; + + return ( +
+
+
+
+ {score} +
+ ); +}; diff --git a/orchestrator/src/client/components/Stats.tsx b/orchestrator/src/client/components/Stats.tsx new file mode 100644 index 0000000..39e2230 --- /dev/null +++ b/orchestrator/src/client/components/Stats.tsx @@ -0,0 +1,50 @@ +/** + * Stats dashboard showing job counts by status. + */ + +import React from 'react'; +import type { JobStatus } from '../../shared/types'; + +interface StatsProps { + stats: Record; +} + +const statConfig: Array<{ + key: JobStatus; + label: string; + emoji: string; +}> = [ + { key: 'discovered', label: 'Discovered', emoji: '🔍' }, + { key: 'processing', label: 'Processing', emoji: '⚙️' }, + { key: 'ready', label: 'Ready', emoji: '✨' }, + { key: 'applied', label: 'Applied', emoji: '✅' }, + { key: 'rejected', label: 'Rejected', emoji: '❌' }, + { key: 'expired', label: 'Expired', emoji: '⏰' }, +]; + +export const Stats: React.FC = ({ stats }) => { + const total = Object.values(stats).reduce((a, b) => a + b, 0); + + return ( +
+
+

Overview

+ + {total} total jobs + +
+ +
+ {statConfig.map(({ key, label, emoji }) => ( +
+
{stats[key] || 0}
+
+ {emoji} + {label} +
+
+ ))} +
+
+ ); +}; diff --git a/orchestrator/src/client/components/StatusBadge.tsx b/orchestrator/src/client/components/StatusBadge.tsx new file mode 100644 index 0000000..c7fe138 --- /dev/null +++ b/orchestrator/src/client/components/StatusBadge.tsx @@ -0,0 +1,28 @@ +/** + * Status badge component. + */ + +import React from 'react'; +import type { JobStatus } from '../../shared/types'; + +interface StatusBadgeProps { + status: JobStatus; +} + +const statusLabels: Record = { + discovered: 'Discovered', + processing: 'Processing', + ready: 'Ready', + applied: 'Applied', + rejected: 'Rejected', + expired: 'Expired', +}; + +export const StatusBadge: React.FC = ({ status }) => { + return ( + + {status === 'processing' && } + {statusLabels[status]} + + ); +}; diff --git a/orchestrator/src/client/components/Toast.tsx b/orchestrator/src/client/components/Toast.tsx new file mode 100644 index 0000000..ae520ac --- /dev/null +++ b/orchestrator/src/client/components/Toast.tsx @@ -0,0 +1,44 @@ +/** + * Toast notification component. + */ + +import React, { useEffect } from 'react'; + +export interface Toast { + id: string; + message: string; + type: 'success' | 'error' | 'info'; +} + +interface ToastContainerProps { + toasts: Toast[]; + onDismiss: (id: string) => void; +} + +export const ToastContainer: React.FC = ({ toasts, onDismiss }) => { + return ( +
+ {toasts.map(toast => ( + + ))} +
+ ); +}; + +const ToastItem: React.FC<{ toast: Toast; onDismiss: (id: string) => void }> = ({ + toast, + onDismiss, +}) => { + useEffect(() => { + const timer = setTimeout(() => { + onDismiss(toast.id); + }, 5000); + return () => clearTimeout(timer); + }, [toast.id, onDismiss]); + + return ( +
+ {toast.message} +
+ ); +}; diff --git a/orchestrator/src/client/components/index.ts b/orchestrator/src/client/components/index.ts new file mode 100644 index 0000000..362abdb --- /dev/null +++ b/orchestrator/src/client/components/index.ts @@ -0,0 +1,8 @@ +export { Header } from './Header'; +export { Stats } from './Stats'; +export { StatusBadge } from './StatusBadge'; +export { ScoreIndicator } from './ScoreIndicator'; +export { JobCard } from './JobCard'; +export { JobList } from './JobList'; +export { ToastContainer, type Toast } from './Toast'; +export * from './Icons'; diff --git a/orchestrator/src/client/main.tsx b/orchestrator/src/client/main.tsx new file mode 100644 index 0000000..f8136ef --- /dev/null +++ b/orchestrator/src/client/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { App } from './App'; +import './styles/index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/orchestrator/src/client/styles/index.css b/orchestrator/src/client/styles/index.css new file mode 100644 index 0000000..3d4ab42 --- /dev/null +++ b/orchestrator/src/client/styles/index.css @@ -0,0 +1,680 @@ +/* =================================================================== + Job Ops Orchestrator - Design System + A modern, dark-mode first design with glassmorphism and gradients + =================================================================== */ + +/* CSS Custom Properties (Design Tokens) */ +:root { + /* Colors */ + --color-background: #0a0a0f; + --color-surface: #12121a; + --color-surface-elevated: #1a1a25; + --color-surface-glass: rgba(26, 26, 37, 0.7); + + --color-border: rgba(255, 255, 255, 0.08); + --color-border-light: rgba(255, 255, 255, 0.12); + + --color-text-primary: #f5f5f7; + --color-text-secondary: #a1a1aa; + --color-text-muted: #71717a; + + /* Accent colors */ + --color-primary: #6366f1; + --color-primary-light: #818cf8; + --color-primary-dark: #4f46e5; + --color-primary-glow: rgba(99, 102, 241, 0.3); + + --color-success: #10b981; + --color-success-light: #34d399; + --color-success-glow: rgba(16, 185, 129, 0.2); + + --color-warning: #f59e0b; + --color-warning-light: #fbbf24; + + --color-danger: #ef4444; + --color-danger-light: #f87171; + --color-danger-glow: rgba(239, 68, 68, 0.2); + + --color-info: #3b82f6; + --color-info-light: #60a5fa; + + /* Gradients */ + --gradient-primary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + --gradient-success: linear-gradient(135deg, #10b981 0%, #34d399 100%); + --gradient-mesh: radial-gradient(at 40% 20%, hsla(250, 80%, 60%, 0.1) 0px, transparent 50%), + radial-gradient(at 80% 0%, hsla(280, 80%, 50%, 0.1) 0px, transparent 50%), + radial-gradient(at 0% 50%, hsla(220, 100%, 60%, 0.05) 0px, transparent 50%); + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'SF Mono', Monaco, 'Cascadia Code', monospace; + + /* Spacing */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + /* Border Radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-2xl: 1.5rem; + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4); + --shadow-glow: 0 0 20px var(--color-primary-glow); + + /* Transitions */ + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-normal: 200ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Reset & Base */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-size: 16px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: var(--font-sans); + background-color: var(--color-background); + color: var(--color-text-primary); + line-height: 1.6; + min-height: 100vh; +} + +#root { + min-height: 100vh; + background-image: var(--gradient-mesh); + background-attachment: fixed; +} + +/* Typography */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.3; + letter-spacing: -0.02em; +} + +h1 { font-size: 2rem; } +h2 { font-size: 1.5rem; } +h3 { font-size: 1.25rem; } +h4 { font-size: 1.125rem; } + +a { + color: var(--color-primary-light); + text-decoration: none; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--color-primary); +} + +/* Utility Classes */ +.container { + max-width: 1400px; + margin: 0 auto; + padding: 0 var(--space-6); +} + +.flex { display: flex; } +.flex-col { flex-direction: column; } +.items-center { align-items: center; } +.justify-between { justify-content: space-between; } +.gap-2 { gap: var(--space-2); } +.gap-3 { gap: var(--space-3); } +.gap-4 { gap: var(--space-4); } +.gap-6 { gap: var(--space-6); } + +/* Glass Card */ +.card { + background: var(--color-surface-glass); + backdrop-filter: blur(12px); + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + padding: var(--space-6); + transition: all var(--transition-normal); +} + +.card:hover { + border-color: var(--color-border-light); + box-shadow: var(--shadow-lg); +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-4); +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-5); + font-family: var(--font-sans); + font-size: 0.875rem; + font-weight: 500; + border: none; + border-radius: var(--radius-lg); + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background: var(--gradient-primary); + color: white; + box-shadow: var(--shadow-sm), 0 0 20px var(--color-primary-glow); +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: var(--shadow-md), 0 0 30px var(--color-primary-glow); +} + +.btn-success { + background: var(--gradient-success); + color: white; +} + +.btn-success:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: var(--shadow-md), 0 0 20px var(--color-success-glow); +} + +.btn-danger { + background: var(--color-danger); + color: white; +} + +.btn-danger:hover:not(:disabled) { + background: var(--color-danger-light); +} + +.btn-ghost { + background: transparent; + color: var(--color-text-secondary); + border: 1px solid var(--color-border); +} + +.btn-ghost:hover:not(:disabled) { + background: var(--color-surface); + color: var(--color-text-primary); + border-color: var(--color-border-light); +} + +.btn-icon { + padding: var(--space-2); + border-radius: var(--radius-md); +} + +/* Status Badges */ +.badge { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1) var(--space-3); + font-size: 0.75rem; + font-weight: 500; + border-radius: var(--radius-full); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.badge-discovered { + background: rgba(59, 130, 246, 0.15); + color: var(--color-info-light); + border: 1px solid rgba(59, 130, 246, 0.3); +} + +.badge-processing { + background: rgba(245, 158, 11, 0.15); + color: var(--color-warning-light); + border: 1px solid rgba(245, 158, 11, 0.3); +} + +.badge-ready { + background: rgba(16, 185, 129, 0.15); + color: var(--color-success-light); + border: 1px solid rgba(16, 185, 129, 0.3); +} + +.badge-applied { + background: rgba(99, 102, 241, 0.15); + color: var(--color-primary-light); + border: 1px solid rgba(99, 102, 241, 0.3); +} + +.badge-rejected { + background: rgba(239, 68, 68, 0.15); + color: var(--color-danger-light); + border: 1px solid rgba(239, 68, 68, 0.3); +} + +.badge-expired { + background: rgba(113, 113, 122, 0.15); + color: var(--color-text-muted); + border: 1px solid rgba(113, 113, 122, 0.3); +} + +/* Score indicator */ +.score { + display: flex; + align-items: center; + gap: var(--space-2); + font-weight: 600; + font-size: 0.875rem; +} + +.score-bar { + width: 60px; + height: 6px; + background: var(--color-surface); + border-radius: var(--radius-full); + overflow: hidden; +} + +.score-bar-fill { + height: 100%; + border-radius: var(--radius-full); + transition: width var(--transition-slow); +} + +.score-high .score-bar-fill { background: var(--color-success); } +.score-medium .score-bar-fill { background: var(--color-warning); } +.score-low .score-bar-fill { background: var(--color-danger); } + +/* Stats Grid */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: var(--space-4); +} + +.stat-card { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: var(--space-4); + text-align: center; +} + +.stat-value { + font-size: 2rem; + font-weight: 700; + background: var(--gradient-primary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.stat-label { + font-size: 0.75rem; + color: var(--color-text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-top: var(--space-1); +} + +/* Job List */ +.job-list { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.job-card { + background: var(--color-surface-glass); + backdrop-filter: blur(12px); + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + padding: var(--space-5); + transition: all var(--transition-normal); +} + +.job-card:hover { + border-color: var(--color-primary); + box-shadow: var(--shadow-glow); + transform: translateY(-2px); +} + +.job-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-4); + margin-bottom: var(--space-3); +} + +.job-title { + font-size: 1.125rem; + font-weight: 600; + color: var(--color-text-primary); + margin-bottom: var(--space-1); +} + +.job-employer { + font-size: 0.875rem; + color: var(--color-text-secondary); +} + +.job-meta { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); + margin-top: var(--space-3); + padding-top: var(--space-3); + border-top: 1px solid var(--color-border); +} + +.job-meta-item { + display: flex; + align-items: center; + gap: var(--space-2); + font-size: 0.8125rem; + color: var(--color-text-muted); +} + +.job-meta-item svg { + width: 14px; + height: 14px; + opacity: 0.7; +} + +.job-actions { + display: flex; + gap: var(--space-2); + margin-top: var(--space-4); +} + +/* Tabs */ +.tabs { + display: flex; + gap: var(--space-1); + background: var(--color-surface); + padding: var(--space-1); + border-radius: var(--radius-lg); + margin-bottom: var(--space-6); +} + +.tab { + flex: 1; + padding: var(--space-3) var(--space-4); + font-size: 0.875rem; + font-weight: 500; + color: var(--color-text-muted); + background: transparent; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-fast); +} + +.tab:hover { + color: var(--color-text-secondary); +} + +.tab.active { + background: var(--gradient-primary); + color: white; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: var(--space-12); + color: var(--color-text-muted); +} + +.empty-state-icon { + font-size: 3rem; + margin-bottom: var(--space-4); + opacity: 0.5; +} + +.empty-state-title { + font-size: 1.125rem; + font-weight: 600; + color: var(--color-text-secondary); + margin-bottom: var(--space-2); +} + +/* Loading Spinner */ +.spinner { + width: 20px; + height: 20px; + border: 2px solid var(--color-border); + border-top-color: var(--color-primary); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Pulse animation for processing jobs */ +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.pulse { + animation: pulse 2s ease-in-out infinite; +} + +/* Header */ +.header { + padding: var(--space-6) 0; + border-bottom: 1px solid var(--color-border); + margin-bottom: var(--space-8); +} + +.header-content { + display: flex; + align-items: center; + justify-content: space-between; +} + +.logo { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.logo-icon { + width: 40px; + height: 40px; + border-radius: var(--radius-lg); + background: var(--gradient-primary); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.25rem; +} + +.logo-text { + font-size: 1.25rem; + font-weight: 700; +} + +/* Modal */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + opacity: 0; + animation: fadeIn var(--transition-fast) forwards; +} + +@keyframes fadeIn { + to { opacity: 1; } +} + +.modal { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-2xl); + max-width: 600px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + animation: slideUp var(--transition-normal) forwards; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-5) var(--space-6); + border-bottom: 1px solid var(--color-border); +} + +.modal-title { + font-size: 1.125rem; + font-weight: 600; +} + +.modal-body { + padding: var(--space-6); +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: var(--space-3); + padding: var(--space-4) var(--space-6); + border-top: 1px solid var(--color-border); + background: var(--color-surface-elevated); + border-radius: 0 0 var(--radius-2xl) var(--radius-2xl); +} + +/* Toast */ +.toast-container { + position: fixed; + bottom: var(--space-6); + right: var(--space-6); + z-index: 200; + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.toast { + background: var(--color-surface-elevated); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: var(--space-4) var(--space-5); + box-shadow: var(--shadow-lg); + min-width: 280px; + animation: slideIn var(--transition-normal) forwards; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.toast-success { border-left: 3px solid var(--color-success); } +.toast-error { border-left: 3px solid var(--color-danger); } +.toast-info { border-left: 3px solid var(--color-info); } + +/* Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--color-surface); +} + +::-webkit-scrollbar-thumb { + background: var(--color-border-light); + border-radius: var(--radius-full); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color-text-muted); +} + +/* Responsive */ +@media (max-width: 768px) { + .container { + padding: 0 var(--space-4); + } + + h1 { font-size: 1.5rem; } + + .header-content { + flex-direction: column; + gap: var(--space-4); + align-items: flex-start; + } + + .stats-grid { + grid-template-columns: repeat(3, 1fr); + } + + .job-card-header { + flex-direction: column; + } + + .job-actions { + flex-direction: column; + } + + .job-actions .btn { + width: 100%; + } +} diff --git a/orchestrator/src/server/api/index.ts b/orchestrator/src/server/api/index.ts new file mode 100644 index 0000000..11a6f1e --- /dev/null +++ b/orchestrator/src/server/api/index.ts @@ -0,0 +1 @@ +export { apiRouter } from './routes.js'; diff --git a/orchestrator/src/server/api/routes.ts b/orchestrator/src/server/api/routes.ts new file mode 100644 index 0000000..1c3f4f1 --- /dev/null +++ b/orchestrator/src/server/api/routes.ts @@ -0,0 +1,272 @@ +/** + * API routes for the orchestrator. + */ + +import { Router, Request, Response } from 'express'; +import { z } from 'zod'; +import * as jobsRepo from '../repositories/jobs.js'; +import * as pipelineRepo from '../repositories/pipeline.js'; +import { runPipeline, processJob, getPipelineStatus } from '../pipeline/index.js'; +import { createNotionEntry } from '../services/notion.js'; +import type { JobStatus, ApiResponse, JobsListResponse, PipelineStatusResponse } from '../../shared/types.js'; + +export const apiRouter = Router(); + +// ============================================================================ +// Jobs API +// ============================================================================ + +/** + * GET /api/jobs - List all jobs + * Query params: status (comma-separated list of statuses to filter) + */ +apiRouter.get('/jobs', async (req: Request, res: Response) => { + try { + const statusFilter = req.query.status as string | undefined; + const statuses = statusFilter?.split(',').filter(Boolean) as JobStatus[] | undefined; + + const jobs = await jobsRepo.getAllJobs(statuses); + const stats = await jobsRepo.getJobStats(); + + const response: ApiResponse = { + success: true, + data: { + jobs, + total: jobs.length, + byStatus: stats, + }, + }; + + res.json(response); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +/** + * GET /api/jobs/:id - Get a single job + */ +apiRouter.get('/jobs/:id', async (req: Request, res: Response) => { + try { + const job = await jobsRepo.getJobById(req.params.id); + + if (!job) { + return res.status(404).json({ success: false, error: 'Job not found' }); + } + + res.json({ success: true, data: job }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +/** + * PATCH /api/jobs/:id - Update a job + */ +const updateJobSchema = z.object({ + status: z.enum(['discovered', 'processing', 'ready', 'applied', 'rejected', 'expired']).optional(), + suitabilityScore: z.number().min(0).max(100).optional(), + suitabilityReason: z.string().optional(), + tailoredSummary: z.string().optional(), + pdfPath: z.string().optional(), +}); + +apiRouter.patch('/jobs/:id', async (req: Request, res: Response) => { + try { + const input = updateJobSchema.parse(req.body); + const job = await jobsRepo.updateJob(req.params.id, input); + + if (!job) { + return res.status(404).json({ success: false, error: 'Job not found' }); + } + + res.json({ success: true, data: job }); + } catch (error) { + if (error instanceof z.ZodError) { + return res.status(400).json({ success: false, error: error.message }); + } + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +/** + * POST /api/jobs/:id/process - Process a single job (generate summary + PDF) + */ +apiRouter.post('/jobs/:id/process', async (req: Request, res: Response) => { + try { + const result = await processJob(req.params.id); + + if (!result.success) { + return res.status(400).json({ success: false, error: result.error }); + } + + const job = await jobsRepo.getJobById(req.params.id); + res.json({ success: true, data: job }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +/** + * POST /api/jobs/:id/apply - Mark a job as applied and sync to Notion + */ +apiRouter.post('/jobs/:id/apply', async (req: Request, res: Response) => { + try { + const job = await jobsRepo.getJobById(req.params.id); + + if (!job) { + return res.status(404).json({ success: false, error: 'Job not found' }); + } + + const appliedAt = new Date().toISOString(); + + // Sync to Notion + const notionResult = await createNotionEntry({ + id: job.id, + title: job.title, + employer: job.employer, + applicationLink: job.applicationLink, + deadline: job.deadline, + salary: job.salary, + location: job.location, + pdfPath: job.pdfPath, + appliedAt, + }); + + // Update job status + const updatedJob = await jobsRepo.updateJob(job.id, { + status: 'applied', + appliedAt, + notionPageId: notionResult.pageId, + }); + + res.json({ success: true, data: updatedJob }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +/** + * POST /api/jobs/:id/reject - Mark a job as rejected + */ +apiRouter.post('/jobs/:id/reject', async (req: Request, res: Response) => { + try { + const job = await jobsRepo.updateJob(req.params.id, { status: 'rejected' }); + + if (!job) { + return res.status(404).json({ success: false, error: 'Job not found' }); + } + + res.json({ success: true, data: job }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +// ============================================================================ +// Pipeline API +// ============================================================================ + +/** + * GET /api/pipeline/status - Get pipeline status + */ +apiRouter.get('/pipeline/status', async (req: Request, res: Response) => { + try { + const { isRunning } = getPipelineStatus(); + const lastRun = await pipelineRepo.getLatestPipelineRun(); + + const response: ApiResponse = { + success: true, + data: { + isRunning, + lastRun, + nextScheduledRun: null, // Would come from n8n + }, + }; + + res.json(response); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +/** + * GET /api/pipeline/runs - Get recent pipeline runs + */ +apiRouter.get('/pipeline/runs', async (req: Request, res: Response) => { + try { + const runs = await pipelineRepo.getRecentPipelineRuns(20); + res.json({ success: true, data: runs }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +/** + * POST /api/pipeline/run - Trigger the pipeline manually + */ +const runPipelineSchema = z.object({ + topN: z.number().min(1).max(50).optional(), + minSuitabilityScore: z.number().min(0).max(100).optional(), +}); + +apiRouter.post('/pipeline/run', async (req: Request, res: Response) => { + try { + const config = runPipelineSchema.parse(req.body); + + // Start pipeline in background + runPipeline(config).catch(console.error); + + res.json({ + success: true, + data: { message: 'Pipeline started' } + }); + } catch (error) { + if (error instanceof z.ZodError) { + return res.status(400).json({ success: false, error: error.message }); + } + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + +// ============================================================================ +// Webhook for n8n +// ============================================================================ + +/** + * POST /api/webhook/trigger - Webhook endpoint for n8n to trigger the pipeline + */ +apiRouter.post('/webhook/trigger', async (req: Request, res: Response) => { + // Optional: Add authentication check + const authHeader = req.headers.authorization; + const expectedToken = process.env.WEBHOOK_SECRET; + + if (expectedToken && authHeader !== `Bearer ${expectedToken}`) { + return res.status(401).json({ success: false, error: 'Unauthorized' }); + } + + try { + // Start pipeline in background + runPipeline().catch(console.error); + + res.json({ + success: true, + data: { + message: 'Pipeline triggered', + triggeredAt: new Date().toISOString(), + } + }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); diff --git a/orchestrator/src/server/db/index.ts b/orchestrator/src/server/db/index.ts new file mode 100644 index 0000000..24fcab9 --- /dev/null +++ b/orchestrator/src/server/db/index.ts @@ -0,0 +1,30 @@ +/** + * Database connection and initialization. + */ + +import Database from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { existsSync, mkdirSync } from 'fs'; +import * as schema from './schema.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const DB_PATH = join(__dirname, '../../../data/jobs.db'); + +// Ensure data directory exists +const dataDir = dirname(DB_PATH); +if (!existsSync(dataDir)) { + mkdirSync(dataDir, { recursive: true }); +} + +const sqlite = new Database(DB_PATH); +sqlite.pragma('journal_mode = WAL'); + +export const db = drizzle(sqlite, { schema }); + +export { schema }; + +export function closeDb() { + sqlite.close(); +} diff --git a/orchestrator/src/server/db/migrate.ts b/orchestrator/src/server/db/migrate.ts new file mode 100644 index 0000000..5487f22 --- /dev/null +++ b/orchestrator/src/server/db/migrate.ts @@ -0,0 +1,77 @@ +/** + * Database migration script - creates tables if they don't exist. + */ + +import Database from 'better-sqlite3'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { existsSync, mkdirSync } from 'fs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const DB_PATH = join(__dirname, '../../../data/jobs.db'); + +// Ensure data directory exists +const dataDir = dirname(DB_PATH); +if (!existsSync(dataDir)) { + mkdirSync(dataDir, { recursive: true }); +} + +const sqlite = new Database(DB_PATH); + +const migrations = [ + `CREATE TABLE IF NOT EXISTS jobs ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + employer TEXT NOT NULL, + employer_url TEXT, + job_url TEXT NOT NULL UNIQUE, + application_link TEXT, + disciplines TEXT, + deadline TEXT, + salary TEXT, + location TEXT, + degree_required TEXT, + starting TEXT, + job_description TEXT, + status TEXT NOT NULL DEFAULT 'discovered' CHECK(status IN ('discovered', 'processing', 'ready', 'applied', 'rejected', 'expired')), + suitability_score REAL, + suitability_reason TEXT, + tailored_summary TEXT, + pdf_path TEXT, + notion_page_id TEXT, + discovered_at TEXT NOT NULL DEFAULT (datetime('now')), + processed_at TEXT, + applied_at TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + )`, + + `CREATE TABLE IF NOT EXISTS pipeline_runs ( + id TEXT PRIMARY KEY, + started_at TEXT NOT NULL DEFAULT (datetime('now')), + completed_at TEXT, + status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running', 'completed', 'failed')), + jobs_discovered INTEGER NOT NULL DEFAULT 0, + jobs_processed INTEGER NOT NULL DEFAULT 0, + error_message TEXT + )`, + + `CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status)`, + `CREATE INDEX IF NOT EXISTS idx_jobs_discovered_at ON jobs(discovered_at)`, + `CREATE INDEX IF NOT EXISTS idx_pipeline_runs_started_at ON pipeline_runs(started_at)`, +]; + +console.log('🔧 Running database migrations...'); + +for (const migration of migrations) { + try { + sqlite.exec(migration); + console.log('✅ Migration applied'); + } catch (error) { + console.error('❌ Migration failed:', error); + process.exit(1); + } +} + +sqlite.close(); +console.log('🎉 Database migrations complete!'); diff --git a/orchestrator/src/server/db/schema.ts b/orchestrator/src/server/db/schema.ts new file mode 100644 index 0000000..42784a8 --- /dev/null +++ b/orchestrator/src/server/db/schema.ts @@ -0,0 +1,58 @@ +/** + * Database schema using Drizzle ORM with SQLite. + */ + +import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core'; +import { sql } from 'drizzle-orm'; + +export const jobs = sqliteTable('jobs', { + id: text('id').primaryKey(), + + // From crawler + title: text('title').notNull(), + employer: text('employer').notNull(), + employerUrl: text('employer_url'), + jobUrl: text('job_url').notNull().unique(), + applicationLink: text('application_link'), + disciplines: text('disciplines'), + deadline: text('deadline'), + salary: text('salary'), + location: text('location'), + degreeRequired: text('degree_required'), + starting: text('starting'), + jobDescription: text('job_description'), + + // Orchestrator enrichments + status: text('status', { + enum: ['discovered', 'processing', 'ready', 'applied', 'rejected', 'expired'] + }).notNull().default('discovered'), + suitabilityScore: real('suitability_score'), + suitabilityReason: text('suitability_reason'), + tailoredSummary: text('tailored_summary'), + pdfPath: text('pdf_path'), + notionPageId: text('notion_page_id'), + + // Timestamps + discoveredAt: text('discovered_at').notNull().default(sql`(datetime('now'))`), + processedAt: text('processed_at'), + appliedAt: text('applied_at'), + createdAt: text('created_at').notNull().default(sql`(datetime('now'))`), + updatedAt: text('updated_at').notNull().default(sql`(datetime('now'))`), +}); + +export const pipelineRuns = sqliteTable('pipeline_runs', { + id: text('id').primaryKey(), + startedAt: text('started_at').notNull().default(sql`(datetime('now'))`), + completedAt: text('completed_at'), + status: text('status', { + enum: ['running', 'completed', 'failed'] + }).notNull().default('running'), + jobsDiscovered: integer('jobs_discovered').notNull().default(0), + jobsProcessed: integer('jobs_processed').notNull().default(0), + errorMessage: text('error_message'), +}); + +export type JobRow = typeof jobs.$inferSelect; +export type NewJobRow = typeof jobs.$inferInsert; +export type PipelineRunRow = typeof pipelineRuns.$inferSelect; +export type NewPipelineRunRow = typeof pipelineRuns.$inferInsert; diff --git a/orchestrator/src/server/index.ts b/orchestrator/src/server/index.ts new file mode 100644 index 0000000..a564c41 --- /dev/null +++ b/orchestrator/src/server/index.ts @@ -0,0 +1,71 @@ +/** + * Express server entry point. + */ + +import express from 'express'; +import cors from 'cors'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { config } from 'dotenv'; +import { apiRouter } from './api/index.js'; + +// Load environment variables from orchestrator root +const __dirname = dirname(fileURLToPath(import.meta.url)); +config({ path: join(__dirname, '../../.env') }); + +const app = express(); +const PORT = process.env.PORT || 3001; + +// Middleware +app.use(cors()); +app.use(express.json()); + +// Logging middleware +app.use((req, res, next) => { + const start = Date.now(); + res.on('finish', () => { + const duration = Date.now() - start; + console.log(`${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`); + }); + next(); +}); + +// API routes +app.use('/api', apiRouter); + +// Serve static files for generated PDFs +const pdfDir = join(__dirname, '../../data/pdfs'); +app.use('/pdfs', express.static(pdfDir)); + +// Health check +app.get('/health', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +// Serve client app in production +if (process.env.NODE_ENV === 'production') { + const clientDir = join(__dirname, '../../dist/client'); + app.use(express.static(clientDir)); + + // SPA fallback + app.get('*', (req, res) => { + res.sendFile(join(clientDir, 'index.html')); + }); +} + +// Start server +app.listen(PORT, () => { + console.log(` +╔═══════════════════════════════════════════════════════════╗ +║ ║ +║ 🚀 Job Ops Orchestrator ║ +║ ║ +║ Server running at: http://localhost:${PORT} ║ +║ ║ +║ API: http://localhost:${PORT}/api ║ +║ Health: http://localhost:${PORT}/health ║ +║ PDFs: http://localhost:${PORT}/pdfs ║ +║ ║ +╚═══════════════════════════════════════════════════════════╝ + `); +}); diff --git a/orchestrator/src/server/pipeline/index.ts b/orchestrator/src/server/pipeline/index.ts new file mode 100644 index 0000000..47060d4 --- /dev/null +++ b/orchestrator/src/server/pipeline/index.ts @@ -0,0 +1 @@ +export * from './orchestrator.js'; diff --git a/orchestrator/src/server/pipeline/orchestrator.ts b/orchestrator/src/server/pipeline/orchestrator.ts new file mode 100644 index 0000000..dd2b3e7 --- /dev/null +++ b/orchestrator/src/server/pipeline/orchestrator.ts @@ -0,0 +1,283 @@ +/** + * Main pipeline logic - orchestrates the daily job processing flow. + * + * Flow: + * 1. Run crawler to discover new jobs + * 2. Score jobs for suitability + * 3. Pick top N jobs + * 4. Generate tailored summaries + * 5. Generate PDF resumes + * 6. Mark as "ready" for user review + */ + +import { readFile } from 'fs/promises'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { runCrawler } from '../services/crawler.js'; +import { scoreAndRankJobs } from '../services/scorer.js'; +import { generateSummary } from '../services/summary.js'; +import { generatePdf } from '../services/pdf.js'; +import * as jobsRepo from '../repositories/jobs.js'; +import * as pipelineRepo from '../repositories/pipeline.js'; +import type { Job, PipelineConfig } from '../../shared/types.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const DEFAULT_PROFILE_PATH = join(__dirname, '../../../../resume-generator/base.json'); + +const DEFAULT_CONFIG: PipelineConfig = { + topN: 10, + minSuitabilityScore: 50, + sources: ['gradcracker'], + profilePath: DEFAULT_PROFILE_PATH, + outputDir: join(__dirname, '../../../data/pdfs'), +}; + +// Track if pipeline is currently running +let isPipelineRunning = false; + +/** + * Run the full job discovery and processing pipeline. + */ +export async function runPipeline(config: Partial = {}): Promise<{ + success: boolean; + jobsDiscovered: number; + jobsProcessed: number; + error?: string; +}> { + if (isPipelineRunning) { + return { + success: false, + jobsDiscovered: 0, + jobsProcessed: 0, + error: 'Pipeline is already running', + }; + } + + isPipelineRunning = true; + const mergedConfig = { ...DEFAULT_CONFIG, ...config }; + + // Create pipeline run record + const pipelineRun = await pipelineRepo.createPipelineRun(); + + console.log('🚀 Starting job pipeline...'); + console.log(` Config: topN=${mergedConfig.topN}, minScore=${mergedConfig.minSuitabilityScore}`); + + try { + // Step 1: Load profile + console.log('\n📋 Loading profile...'); + const profile = await loadProfile(mergedConfig.profilePath); + + // Step 2: Run crawler + console.log('\n🕷️ Running crawler...'); + const crawlerResult = await runCrawler(); + + if (!crawlerResult.success) { + throw new Error(`Crawler failed: ${crawlerResult.error}`); + } + + // Step 3: Import discovered jobs + console.log('\n💾 Importing jobs to database...'); + const { created, skipped } = await jobsRepo.bulkCreateJobs(crawlerResult.jobs); + console.log(` Created: ${created}, Skipped (duplicates): ${skipped}`); + + await pipelineRepo.updatePipelineRun(pipelineRun.id, { + jobsDiscovered: created, + }); + + // Step 4: Get unprocessed jobs and score them + console.log('\n🎯 Scoring jobs for suitability...'); + const unprocessedJobs = await jobsRepo.getJobsForProcessing(50); // Get more than topN for ranking + const rankedJobs = await scoreAndRankJobs(unprocessedJobs, profile); + + // Update scores in database + for (const job of rankedJobs) { + await jobsRepo.updateJob(job.id, { + suitabilityScore: job.suitabilityScore, + suitabilityReason: job.suitabilityReason, + }); + } + + // Step 5: Pick top N jobs above threshold + const topJobs = rankedJobs + .filter(j => j.suitabilityScore >= mergedConfig.minSuitabilityScore) + .slice(0, mergedConfig.topN); + + console.log(`\n📊 Selected ${topJobs.length} top jobs for processing:`); + for (const job of topJobs) { + console.log(` - ${job.title} @ ${job.employer} (score: ${job.suitabilityScore})`); + } + + // Step 6: Process each top job + let processed = 0; + + for (const job of topJobs) { + console.log(`\n📝 Processing: ${job.title} @ ${job.employer}`); + + try { + // Mark as processing + await jobsRepo.updateJob(job.id, { status: 'processing' }); + + // Generate tailored summary + console.log(' Generating summary...'); + const summaryResult = await generateSummary( + job.jobDescription || '', + profile + ); + + if (!summaryResult.success) { + console.warn(` ⚠️ Summary generation failed: ${summaryResult.error}`); + continue; + } + + // Update job with summary + await jobsRepo.updateJob(job.id, { + tailoredSummary: summaryResult.summary, + }); + + // Generate PDF + console.log(' Generating PDF...'); + const pdfResult = await generatePdf( + job.id, + summaryResult.summary!, + mergedConfig.profilePath + ); + + if (!pdfResult.success) { + console.warn(` ⚠️ PDF generation failed: ${pdfResult.error}`); + // Still mark as ready even if PDF failed - user can regenerate + } + + // Mark as ready + await jobsRepo.updateJob(job.id, { + status: 'ready', + pdfPath: pdfResult.pdfPath ?? null, + }); + + processed++; + console.log(` ✅ Ready for review!`); + + } catch (error) { + console.error(` ❌ Failed to process job: ${error}`); + // Continue with next job + } + } + + // Update pipeline run as completed + await pipelineRepo.updatePipelineRun(pipelineRun.id, { + status: 'completed', + completedAt: new Date().toISOString(), + jobsProcessed: processed, + }); + + console.log('\n🎉 Pipeline completed!'); + console.log(` Jobs discovered: ${created}`); + console.log(` Jobs processed: ${processed}`); + + isPipelineRunning = false; + + return { + success: true, + jobsDiscovered: created, + jobsProcessed: processed, + }; + + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + + await pipelineRepo.updatePipelineRun(pipelineRun.id, { + status: 'failed', + completedAt: new Date().toISOString(), + errorMessage: message, + }); + + isPipelineRunning = false; + + console.error('\n❌ Pipeline failed:', message); + + return { + success: false, + jobsDiscovered: 0, + jobsProcessed: 0, + error: message, + }; + } +} + +/** + * Process a single job (for manual processing). + */ +export async function processJob(jobId: string): Promise<{ + success: boolean; + error?: string; +}> { + console.log(`📝 Processing job ${jobId}...`); + + try { + const job = await jobsRepo.getJobById(jobId); + if (!job) { + return { success: false, error: 'Job not found' }; + } + + const profile = await loadProfile(DEFAULT_PROFILE_PATH); + + // Mark as processing + await jobsRepo.updateJob(job.id, { status: 'processing' }); + + // Generate summary if not already done + if (!job.tailoredSummary) { + console.log(' Generating summary...'); + const summaryResult = await generateSummary( + job.jobDescription || '', + profile + ); + + if (summaryResult.success) { + await jobsRepo.updateJob(job.id, { + tailoredSummary: summaryResult.summary, + }); + job.tailoredSummary = summaryResult.summary ?? null; + } + } + + // Generate PDF + console.log(' Generating PDF...'); + const pdfResult = await generatePdf( + job.id, + job.tailoredSummary || '', + DEFAULT_PROFILE_PATH + ); + + // Mark as ready + await jobsRepo.updateJob(job.id, { + status: 'ready', + pdfPath: pdfResult.pdfPath ?? null, + }); + + console.log(' ✅ Done!'); + return { success: true }; + + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: message }; + } +} + +/** + * Check if pipeline is currently running. + */ +export function getPipelineStatus(): { isRunning: boolean } { + return { isRunning: isPipelineRunning }; +} + +/** + * Load the user profile from JSON file. + */ +async function loadProfile(profilePath: string): Promise> { + try { + const content = await readFile(profilePath, 'utf-8'); + return JSON.parse(content); + } catch (error) { + console.warn('Failed to load profile, using empty object'); + return {}; + } +} diff --git a/orchestrator/src/server/pipeline/run.ts b/orchestrator/src/server/pipeline/run.ts new file mode 100644 index 0000000..b8e1e1a --- /dev/null +++ b/orchestrator/src/server/pipeline/run.ts @@ -0,0 +1,45 @@ +/** + * Standalone script to run the pipeline. + * Can be triggered by n8n or cron. + * + * Usage: npm run pipeline:run + */ + +import { config } from 'dotenv'; +import { runPipeline } from './orchestrator.js'; +import { closeDb } from '../db/index.js'; + +// Load environment variables +config(); + +async function main() { + console.log('='.repeat(60)); + console.log('🚀 Job Pipeline Runner'); + console.log(` Started at: ${new Date().toISOString()}`); + console.log('='.repeat(60)); + + const result = await runPipeline({ + topN: parseInt(process.env.PIPELINE_TOP_N || '10'), + minSuitabilityScore: parseInt(process.env.PIPELINE_MIN_SCORE || '50'), + }); + + console.log('\n' + '='.repeat(60)); + console.log('📊 Pipeline Results:'); + console.log(` Success: ${result.success}`); + console.log(` Jobs Discovered: ${result.jobsDiscovered}`); + console.log(` Jobs Processed: ${result.jobsProcessed}`); + if (result.error) { + console.log(` Error: ${result.error}`); + } + console.log(` Completed at: ${new Date().toISOString()}`); + console.log('='.repeat(60)); + + closeDb(); + process.exit(result.success ? 0 : 1); +} + +main().catch((error) => { + console.error('Fatal error:', error); + closeDb(); + process.exit(1); +}); diff --git a/orchestrator/src/server/repositories/index.ts b/orchestrator/src/server/repositories/index.ts new file mode 100644 index 0000000..25a0848 --- /dev/null +++ b/orchestrator/src/server/repositories/index.ts @@ -0,0 +1,2 @@ +export * from './jobs.js'; +export * from './pipeline.js'; diff --git a/orchestrator/src/server/repositories/jobs.ts b/orchestrator/src/server/repositories/jobs.ts new file mode 100644 index 0000000..a280893 --- /dev/null +++ b/orchestrator/src/server/repositories/jobs.ts @@ -0,0 +1,190 @@ +/** + * Job repository - data access layer for jobs. + */ + +import { eq, desc, sql, and, inArray } from 'drizzle-orm'; +import { randomUUID } from 'crypto'; +import { db, schema } from '../db/index.js'; +import type { Job, CreateJobInput, UpdateJobInput, JobStatus } from '../../shared/types.js'; + +const { jobs } = schema; + +/** + * Get all jobs, optionally filtered by status. + */ +export async function getAllJobs(statuses?: JobStatus[]): Promise { + const query = statuses && statuses.length > 0 + ? db.select().from(jobs).where(inArray(jobs.status, statuses)).orderBy(desc(jobs.discoveredAt)) + : db.select().from(jobs).orderBy(desc(jobs.discoveredAt)); + + const rows = await query; + return rows.map(mapRowToJob); +} + +/** + * Get a single job by ID. + */ +export async function getJobById(id: string): Promise { + const [row] = await db.select().from(jobs).where(eq(jobs.id, id)); + return row ? mapRowToJob(row) : null; +} + +/** + * Get a job by its URL (for deduplication). + */ +export async function getJobByUrl(jobUrl: string): Promise { + const [row] = await db.select().from(jobs).where(eq(jobs.jobUrl, jobUrl)); + return row ? mapRowToJob(row) : null; +} + +/** + * Create a new job (or return existing if URL matches). + */ +export async function createJob(input: CreateJobInput): Promise { + // Check for existing job with same URL + const existing = await getJobByUrl(input.jobUrl); + if (existing) { + return existing; + } + + const id = randomUUID(); + const now = new Date().toISOString(); + + await db.insert(jobs).values({ + id, + title: input.title, + employer: input.employer, + employerUrl: input.employerUrl ?? null, + jobUrl: input.jobUrl, + applicationLink: input.applicationLink ?? null, + disciplines: input.disciplines ?? null, + deadline: input.deadline ?? null, + salary: input.salary ?? null, + location: input.location ?? null, + degreeRequired: input.degreeRequired ?? null, + starting: input.starting ?? null, + jobDescription: input.jobDescription ?? null, + status: 'discovered', + discoveredAt: now, + createdAt: now, + updatedAt: now, + }); + + return (await getJobById(id))!; +} + +/** + * Update a job. + */ +export async function updateJob(id: string, input: UpdateJobInput): Promise { + const now = new Date().toISOString(); + + await db.update(jobs) + .set({ + ...input, + updatedAt: now, + ...(input.status === 'processing' ? { processedAt: now } : {}), + ...(input.status === 'applied' && !input.appliedAt ? { appliedAt: now } : {}), + }) + .where(eq(jobs.id, id)); + + return getJobById(id); +} + +/** + * Bulk create jobs from crawler results. + */ +export async function bulkCreateJobs(inputs: CreateJobInput[]): Promise<{ created: number; skipped: number }> { + let created = 0; + let skipped = 0; + + for (const input of inputs) { + const existing = await getJobByUrl(input.jobUrl); + if (existing) { + skipped++; + continue; + } + + await createJob(input); + created++; + } + + return { created, skipped }; +} + +/** + * Get job statistics by status. + */ +export async function getJobStats(): Promise> { + const result = await db + .select({ + status: jobs.status, + count: sql`count(*)`, + }) + .from(jobs) + .groupBy(jobs.status); + + const stats: Record = { + discovered: 0, + processing: 0, + ready: 0, + applied: 0, + rejected: 0, + expired: 0, + }; + + for (const row of result) { + stats[row.status as JobStatus] = row.count; + } + + return stats; +} + +/** + * Get jobs ready for processing (discovered with description). + */ +export async function getJobsForProcessing(limit: number = 10): Promise { + const rows = await db + .select() + .from(jobs) + .where( + and( + eq(jobs.status, 'discovered'), + sql`${jobs.jobDescription} IS NOT NULL` + ) + ) + .orderBy(desc(jobs.discoveredAt)) + .limit(limit); + + return rows.map(mapRowToJob); +} + +// Helper to map database row to Job type +function mapRowToJob(row: typeof jobs.$inferSelect): Job { + return { + id: row.id, + title: row.title, + employer: row.employer, + employerUrl: row.employerUrl, + jobUrl: row.jobUrl, + applicationLink: row.applicationLink, + disciplines: row.disciplines, + deadline: row.deadline, + salary: row.salary, + location: row.location, + degreeRequired: row.degreeRequired, + starting: row.starting, + jobDescription: row.jobDescription, + status: row.status as JobStatus, + suitabilityScore: row.suitabilityScore, + suitabilityReason: row.suitabilityReason, + tailoredSummary: row.tailoredSummary, + pdfPath: row.pdfPath, + notionPageId: row.notionPageId, + discoveredAt: row.discoveredAt, + processedAt: row.processedAt, + appliedAt: row.appliedAt, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + }; +} diff --git a/orchestrator/src/server/repositories/pipeline.ts b/orchestrator/src/server/repositories/pipeline.ts new file mode 100644 index 0000000..2c30be5 --- /dev/null +++ b/orchestrator/src/server/repositories/pipeline.ts @@ -0,0 +1,94 @@ +/** + * Pipeline run repository. + */ + +import { eq, desc } from 'drizzle-orm'; +import { randomUUID } from 'crypto'; +import { db, schema } from '../db/index.js'; +import type { PipelineRun } from '../../shared/types.js'; + +const { pipelineRuns } = schema; + +/** + * Create a new pipeline run. + */ +export async function createPipelineRun(): Promise { + const id = randomUUID(); + const now = new Date().toISOString(); + + await db.insert(pipelineRuns).values({ + id, + startedAt: now, + status: 'running', + }); + + return { + id, + startedAt: now, + completedAt: null, + status: 'running', + jobsDiscovered: 0, + jobsProcessed: 0, + errorMessage: null, + }; +} + +/** + * Update a pipeline run. + */ +export async function updatePipelineRun( + id: string, + update: Partial<{ + completedAt: string; + status: 'running' | 'completed' | 'failed'; + jobsDiscovered: number; + jobsProcessed: number; + errorMessage: string; + }> +): Promise { + await db.update(pipelineRuns).set(update).where(eq(pipelineRuns.id, id)); +} + +/** + * Get the latest pipeline run. + */ +export async function getLatestPipelineRun(): Promise { + const [row] = await db + .select() + .from(pipelineRuns) + .orderBy(desc(pipelineRuns.startedAt)) + .limit(1); + + if (!row) return null; + + return { + id: row.id, + startedAt: row.startedAt, + completedAt: row.completedAt, + status: row.status as PipelineRun['status'], + jobsDiscovered: row.jobsDiscovered, + jobsProcessed: row.jobsProcessed, + errorMessage: row.errorMessage, + }; +} + +/** + * Get recent pipeline runs. + */ +export async function getRecentPipelineRuns(limit: number = 10): Promise { + const rows = await db + .select() + .from(pipelineRuns) + .orderBy(desc(pipelineRuns.startedAt)) + .limit(limit); + + return rows.map(row => ({ + id: row.id, + startedAt: row.startedAt, + completedAt: row.completedAt, + status: row.status as PipelineRun['status'], + jobsDiscovered: row.jobsDiscovered, + jobsProcessed: row.jobsProcessed, + errorMessage: row.errorMessage, + })); +} diff --git a/orchestrator/src/server/services/crawler.ts b/orchestrator/src/server/services/crawler.ts new file mode 100644 index 0000000..7de78ac --- /dev/null +++ b/orchestrator/src/server/services/crawler.ts @@ -0,0 +1,112 @@ +/** + * Service for running the job crawler (job-extractor). + * Wraps the existing Crawlee-based crawler. + */ + +import { spawn } from 'child_process'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { readdir, readFile } from 'fs/promises'; +import type { CreateJobInput } from '../../shared/types.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const CRAWLER_DIR = join(__dirname, '../../../../job-extractor'); +const STORAGE_DIR = join(CRAWLER_DIR, 'storage/datasets/default'); + +export interface CrawlerResult { + success: boolean; + jobs: CreateJobInput[]; + error?: string; +} + +/** + * Run the job-extractor crawler and return discovered jobs. + */ +export async function runCrawler(): Promise { + console.log('🕷️ Starting job crawler...'); + + try { + // Clear previous results + await clearStorageDataset(); + + // Run the crawler + await new Promise((resolve, reject) => { + const child = spawn('npm', ['run', 'start'], { + cwd: CRAWLER_DIR, + shell: true, + stdio: 'inherit', + }); + + child.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Crawler exited with code ${code}`)); + } + }); + + child.on('error', reject); + }); + + // Read crawled jobs from storage + const jobs = await readCrawledJobs(); + + console.log(`✅ Crawler completed. Found ${jobs.length} jobs.`); + + return { success: true, jobs }; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error('❌ Crawler failed:', message); + return { success: false, jobs: [], error: message }; + } +} + +/** + * Read crawled jobs from the Crawlee storage dataset. + */ +async function readCrawledJobs(): Promise { + try { + const files = await readdir(STORAGE_DIR); + const jsonFiles = files.filter(f => f.endsWith('.json')); + + const jobs: CreateJobInput[] = []; + + for (const file of jsonFiles) { + const content = await readFile(join(STORAGE_DIR, file), 'utf-8'); + const data = JSON.parse(content); + + // Map crawler output to our job input format + jobs.push({ + title: data.title || 'Unknown Title', + employer: data.employer || 'Unknown Employer', + employerUrl: data.employerUrl, + jobUrl: data.url || data.jobUrl, + applicationLink: data.applicationLink, + disciplines: data.disciplines, + deadline: data.deadline, + salary: data.salary, + location: data.location, + degreeRequired: data.degreeRequired, + starting: data.starting, + jobDescription: data.jobDescription, + }); + } + + return jobs; + } catch (error) { + console.error('Failed to read crawled jobs:', error); + return []; + } +} + +/** + * Clear previous crawl results. + */ +async function clearStorageDataset(): Promise { + const { rm } = await import('fs/promises'); + try { + await rm(STORAGE_DIR, { recursive: true, force: true }); + } catch { + // Ignore if directory doesn't exist + } +} diff --git a/orchestrator/src/server/services/index.ts b/orchestrator/src/server/services/index.ts new file mode 100644 index 0000000..43a7e1f --- /dev/null +++ b/orchestrator/src/server/services/index.ts @@ -0,0 +1,5 @@ +export * from './crawler.js'; +export * from './scorer.js'; +export * from './summary.js'; +export * from './pdf.js'; +export * from './notion.js'; diff --git a/orchestrator/src/server/services/notion.ts b/orchestrator/src/server/services/notion.ts new file mode 100644 index 0000000..5a9bf5b --- /dev/null +++ b/orchestrator/src/server/services/notion.ts @@ -0,0 +1,89 @@ +/** + * Service for syncing with Notion. + */ + +export interface NotionSyncResult { + success: boolean; + pageId?: string; + error?: string; +} + +/** + * Create a job entry in Notion. + * + * This is a placeholder - implement based on your Notion setup. + */ +export async function createNotionEntry(job: { + id: string; + title: string; + employer: string; + applicationLink: string | null; + deadline: string | null; + salary: string | null; + location: string | null; + pdfPath: string | null; + appliedAt: string; +}): Promise { + const notionApiKey = process.env.NOTION_API_KEY; + const notionDatabaseId = process.env.NOTION_DATABASE_ID; + + if (!notionApiKey || !notionDatabaseId) { + console.log('ℹ️ Notion API not configured, skipping sync'); + return { success: true, pageId: undefined }; + } + + try { + const response = await fetch('https://api.notion.com/v1/pages', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${notionApiKey}`, + 'Content-Type': 'application/json', + 'Notion-Version': '2022-06-28', + }, + body: JSON.stringify({ + parent: { database_id: notionDatabaseId }, + properties: { + // Customize these based on your Notion database schema + 'Name': { + title: [{ text: { content: `${job.title} @ ${job.employer}` } }], + }, + 'Company': { + rich_text: [{ text: { content: job.employer } }], + }, + 'Application Link': job.applicationLink ? { + url: job.applicationLink, + } : undefined, + 'Deadline': job.deadline ? { + date: { start: job.deadline }, + } : undefined, + 'Salary': job.salary ? { + rich_text: [{ text: { content: job.salary } }], + } : undefined, + 'Location': job.location ? { + rich_text: [{ text: { content: job.location } }], + } : undefined, + 'Applied Date': { + date: { start: job.appliedAt }, + }, + 'Status': { + status: { name: 'Applied' }, + }, + }, + }), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`Notion API error: ${response.status} - ${error}`); + } + + const data = await response.json(); + + console.log(`✅ Created Notion entry: ${data.id}`); + return { success: true, pageId: data.id }; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(`❌ Notion sync failed: ${message}`); + return { success: false, error: message }; + } +} diff --git a/orchestrator/src/server/services/pdf.ts b/orchestrator/src/server/services/pdf.ts new file mode 100644 index 0000000..5df4a36 --- /dev/null +++ b/orchestrator/src/server/services/pdf.ts @@ -0,0 +1,144 @@ +/** + * Service for generating PDF resumes using RXResume. + * Wraps the existing Python rxresume_automation.py script. + */ + +import { spawn } from 'child_process'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { readFile, writeFile, copyFile, access, mkdir } from 'fs/promises'; +import { existsSync } from 'fs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const RESUME_GEN_DIR = join(__dirname, '../../../../resume-generator'); +const OUTPUT_DIR = join(__dirname, '../../../data/pdfs'); + +export interface PdfResult { + success: boolean; + pdfPath?: string; + error?: string; +} + +/** + * Generate a tailored PDF resume for a job. + * + * @param jobId - Unique job identifier (used for filename) + * @param tailoredSummary - The AI-generated summary to inject + * @param baseResumePath - Path to the base resume JSON (optional) + */ +export async function generatePdf( + jobId: string, + tailoredSummary: string, + baseResumePath?: string +): Promise { + console.log(`📄 Generating PDF for job ${jobId}...`); + + const resumeJsonPath = baseResumePath || join(RESUME_GEN_DIR, 'base.json'); + + try { + // Ensure output directory exists + if (!existsSync(OUTPUT_DIR)) { + await mkdir(OUTPUT_DIR, { recursive: true }); + } + + // Read base resume + const baseResume = JSON.parse(await readFile(resumeJsonPath, 'utf-8')); + + // Inject tailored summary + if (baseResume.sections?.summary) { + baseResume.sections.summary.content = tailoredSummary; + } else if (baseResume.basics?.summary) { + baseResume.basics.summary = tailoredSummary; + } + + // Write modified resume to temp file + const tempResumePath = join(RESUME_GEN_DIR, `temp_resume_${jobId}.json`); + await writeFile(tempResumePath, JSON.stringify(baseResume, null, 2)); + + // Generate PDF using Python script + const outputFilename = `resume_${jobId}.pdf`; + const outputPath = join(OUTPUT_DIR, outputFilename); + + await runPythonPdfGenerator(tempResumePath, outputFilename); + + // Move generated PDF to our output directory + const pythonOutputPath = join(RESUME_GEN_DIR, 'resumes', outputFilename); + + try { + await access(pythonOutputPath); + await copyFile(pythonOutputPath, outputPath); + } catch { + // PDF might already be in the right place or script output different location + console.warn('PDF not found at expected Python output location'); + } + + // Cleanup temp file + try { + const { unlink } = await import('fs/promises'); + await unlink(tempResumePath); + } catch { + // Ignore cleanup errors + } + + console.log(`✅ PDF generated: ${outputPath}`); + return { success: true, pdfPath: outputPath }; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(`❌ PDF generation failed: ${message}`); + return { success: false, error: message }; + } +} + +/** + * Run the Python RXResume automation script. + */ +async function runPythonPdfGenerator( + jsonPath: string, + outputFilename: string +): Promise { + return new Promise((resolve, reject) => { + // Note: This calls the Python script with the JSON path + // The Python script needs to be modified to accept these args + // For now, we'll use environment variables + + const child = spawn('python3', ['rxresume_automation.py'], { + cwd: RESUME_GEN_DIR, + env: { + ...process.env, + RESUME_JSON_PATH: jsonPath, + OUTPUT_FILENAME: outputFilename, + }, + stdio: 'inherit', + }); + + child.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Python script exited with code ${code}`)); + } + }); + + child.on('error', reject); + }); +} + +/** + * Check if a PDF exists for a job. + */ +export async function pdfExists(jobId: string): Promise { + const pdfPath = join(OUTPUT_DIR, `resume_${jobId}.pdf`); + try { + await access(pdfPath); + return true; + } catch { + return false; + } +} + +/** + * Get the path to a job's PDF. + */ +export function getPdfPath(jobId: string): string { + return join(OUTPUT_DIR, `resume_${jobId}.pdf`); +} diff --git a/orchestrator/src/server/services/scorer.ts b/orchestrator/src/server/services/scorer.ts new file mode 100644 index 0000000..814739e --- /dev/null +++ b/orchestrator/src/server/services/scorer.ts @@ -0,0 +1,143 @@ +/** + * Service for scoring job suitability using AI. + */ + +import type { Job } from '../../shared/types.js'; + +const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions'; + +interface SuitabilityResult { + score: number; // 0-100 + reason: string; // Explanation +} + +/** + * Score a job's suitability based on profile and job description. + */ +export async function scoreJobSuitability( + job: Job, + profile: Record +): Promise { + const apiKey = process.env.OPENROUTER_API_KEY; + if (!apiKey) { + console.warn('⚠️ OPENROUTER_API_KEY not set, using mock scoring'); + return mockScore(job); + } + + const model = process.env.MODEL || 'openai/gpt-4o-mini'; + + const prompt = buildScoringPrompt(job, profile); + + try { + const response = await fetch(OPENROUTER_API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + 'HTTP-Referer': 'http://localhost', + 'X-Title': 'JobOpsOrchestrator', + }, + body: JSON.stringify({ + model, + messages: [{ role: 'user', content: prompt }], + response_format: { type: 'json_object' }, + }), + }); + + if (!response.ok) { + throw new Error(`OpenRouter error: ${response.status}`); + } + + const data = await response.json(); + const content = data.choices[0]?.message?.content; + + if (!content) { + throw new Error('No content in response'); + } + + const parsed = JSON.parse(content); + return { + score: Math.min(100, Math.max(0, parsed.score || 0)), + reason: parsed.reason || 'No explanation provided', + }; + } catch (error) { + console.error('Failed to score job:', error); + return mockScore(job); + } +} + +function buildScoringPrompt(job: Job, profile: Record): string { + return ` +You are evaluating a job listing for a candidate. Score how suitable this job is for the candidate on a scale of 0-100. + +Consider: +- Skills match (technologies, frameworks, languages) +- Experience level match +- Location/remote work alignment +- Industry/domain fit +- Career growth potential + +Candidate Profile: +${JSON.stringify(profile, null, 2)} + +Job Listing: +Title: ${job.title} +Employer: ${job.employer} +Location: ${job.location || 'Not specified'} +Salary: ${job.salary || 'Not specified'} +Degree Required: ${job.degreeRequired || 'Not specified'} +Disciplines: ${job.disciplines || 'Not specified'} + +Job Description: +${job.jobDescription || 'No description available'} + +Respond with JSON: { "score": <0-100>, "reason": "" } +`; +} + +function mockScore(job: Job): SuitabilityResult { + // Simple keyword-based scoring as fallback + const jd = (job.jobDescription || '').toLowerCase(); + const title = job.title.toLowerCase(); + + const goodKeywords = ['typescript', 'react', 'node', 'python', 'web', 'frontend', 'backend', 'fullstack', 'software', 'engineer', 'developer']; + const badKeywords = ['senior', '5+ years', '10+ years', 'principal', 'staff', 'manager']; + + let score = 50; + + for (const kw of goodKeywords) { + if (jd.includes(kw) || title.includes(kw)) score += 5; + } + + for (const kw of badKeywords) { + if (jd.includes(kw) || title.includes(kw)) score -= 10; + } + + score = Math.min(100, Math.max(0, score)); + + return { + score, + reason: 'Scored using keyword matching (API key not configured)', + }; +} + +/** + * Score multiple jobs and return sorted by score (descending). + */ +export async function scoreAndRankJobs( + jobs: Job[], + profile: Record +): Promise> { + const scoredJobs = await Promise.all( + jobs.map(async (job) => { + const { score, reason } = await scoreJobSuitability(job, profile); + return { + ...job, + suitabilityScore: score, + suitabilityReason: reason, + }; + }) + ); + + return scoredJobs.sort((a, b) => b.suitabilityScore - a.suitabilityScore); +} diff --git a/orchestrator/src/server/services/summary.ts b/orchestrator/src/server/services/summary.ts new file mode 100644 index 0000000..3ef98df --- /dev/null +++ b/orchestrator/src/server/services/summary.ts @@ -0,0 +1,153 @@ +/** + * Service for generating tailored resume summaries. + * Wraps the existing Python generate_summary.py script. + */ + +import { spawn } from 'child_process'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { writeFile, unlink } from 'fs/promises'; +import { randomUUID } from 'crypto'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const RESUME_GEN_DIR = join(__dirname, '../../../../resume-generator'); + +const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions'; + +export interface SummaryResult { + success: boolean; + summary?: string; + error?: string; +} + +/** + * Generate a tailored resume summary for a job. + * Uses the native implementation instead of calling Python. + */ +export async function generateSummary( + jobDescription: string, + profile: Record +): Promise { + const apiKey = process.env.OPENROUTER_API_KEY; + + if (!apiKey) { + console.warn('⚠️ OPENROUTER_API_KEY not set, cannot generate summary'); + return { success: false, error: 'API key not configured' }; + } + + const model = process.env.MODEL || 'openai/gpt-4o-mini'; + + const prompt = buildSummaryPrompt(profile, jobDescription); + + try { + const response = await fetch(OPENROUTER_API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + 'HTTP-Referer': 'http://localhost', + 'X-Title': 'JobOpsOrchestrator', + }, + body: JSON.stringify({ + model, + messages: [{ role: 'user', content: prompt }], + }), + }); + + if (!response.ok) { + throw new Error(`OpenRouter error: ${response.status}`); + } + + const data = await response.json(); + const summary = data.choices[0]?.message?.content; + + if (!summary) { + throw new Error('No content in response'); + } + + return { success: true, summary: summary.trim() }; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: message }; + } +} + +function buildSummaryPrompt(profile: Record, jd: string): string { + return ` +You are generating a tailored résumé summary for me. + +Requirements: +- Use keywords found in the job description. +- Keep it concise but meaningful. Avoid fluff. Avoid long-winded text. +- Include just enough detail to feel real and grounded. +- Gently convey that I care about helping people and doing good work. +- Do NOT invent experience or skills I don't have. +- Maintain a warm, confident, human tone. +- Target THIS specific job directly, so use ATS keywords, while remaining natural. +- Use the profile to add context and details. + +My profile (JSON fields merged): +${JSON.stringify(profile, null, 2)} + +Job description: +${jd} + +Write the résumé summary now. +`; +} + +/** + * Alternative: Call the Python script directly. + * Useful if the Python script has additional functionality. + */ +export async function generateSummaryViaPython( + jobDescription: string +): Promise { + const tempFile = join(RESUME_GEN_DIR, `temp_jd_${randomUUID()}.txt`); + + try { + // Write JD to temp file + await writeFile(tempFile, jobDescription); + + // Call Python script + const result = await new Promise((resolve, reject) => { + let output = ''; + let error = ''; + + const child = spawn('python3', ['generate_summary.py', '--file', tempFile], { + cwd: RESUME_GEN_DIR, + env: { ...process.env }, + }); + + child.stdout.on('data', (data) => { + output += data.toString(); + }); + + child.stderr.on('data', (data) => { + error += data.toString(); + }); + + child.on('close', (code) => { + if (code === 0) { + resolve(output); + } else { + reject(new Error(error || `Process exited with code ${code}`)); + } + }); + + child.on('error', reject); + }); + + return { success: true, summary: result.trim() }; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: message }; + } finally { + // Cleanup temp file + try { + await unlink(tempFile); + } catch { + // Ignore cleanup errors + } + } +} diff --git a/orchestrator/src/shared/index.ts b/orchestrator/src/shared/index.ts new file mode 100644 index 0000000..fcb073f --- /dev/null +++ b/orchestrator/src/shared/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/orchestrator/src/shared/types.ts b/orchestrator/src/shared/types.ts new file mode 100644 index 0000000..f6cd0a0 --- /dev/null +++ b/orchestrator/src/shared/types.ts @@ -0,0 +1,106 @@ +/** + * Shared types for the job-ops orchestrator. + */ + +export type JobStatus = + | 'discovered' // Crawled but not processed + | 'processing' // Currently generating resume + | 'ready' // PDF generated, waiting for user to apply + | 'applied' // User marked as applied (added to Notion) + | 'rejected' // User rejected this job + | 'expired'; // Deadline passed + +export interface Job { + id: string; + + // From crawler + title: string; + employer: string; + employerUrl: string | null; + jobUrl: string; // Gradcracker listing URL + applicationLink: string | null; // Actual application URL + disciplines: string | null; + deadline: string | null; + salary: string | null; + location: string | null; + degreeRequired: string | null; + starting: string | null; + jobDescription: string | null; + + // Orchestrator enrichments + status: JobStatus; + suitabilityScore: number | null; // 0-100 AI-generated score + suitabilityReason: string | null; // AI explanation + tailoredSummary: string | null; // Generated resume summary + pdfPath: string | null; // Path to generated PDF + notionPageId: string | null; // Notion page ID if synced + + // Timestamps + discoveredAt: string; + processedAt: string | null; + appliedAt: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateJobInput { + title: string; + employer: string; + employerUrl?: string; + jobUrl: string; + applicationLink?: string; + disciplines?: string; + deadline?: string; + salary?: string; + location?: string; + degreeRequired?: string; + starting?: string; + jobDescription?: string; +} + +export interface UpdateJobInput { + status?: JobStatus; + suitabilityScore?: number; + suitabilityReason?: string; + tailoredSummary?: string; + pdfPath?: string; + notionPageId?: string; + appliedAt?: string; +} + +export interface PipelineConfig { + topN: number; // Number of top jobs to process + minSuitabilityScore: number; // Minimum score to auto-process + sources: string[]; // Job sources to crawl + profilePath: string; // Path to profile JSON + outputDir: string; // Directory for generated PDFs +} + +export interface PipelineRun { + id: string; + startedAt: string; + completedAt: string | null; + status: 'running' | 'completed' | 'failed'; + jobsDiscovered: number; + jobsProcessed: number; + errorMessage: string | null; +} + +// API Response types +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; +} + +export interface JobsListResponse { + jobs: Job[]; + total: number; + byStatus: Record; +} + +export interface PipelineStatusResponse { + isRunning: boolean; + lastRun: PipelineRun | null; + nextScheduledRun: string | null; +} diff --git a/orchestrator/tsconfig.json b/orchestrator/tsconfig.json new file mode 100644 index 0000000..2f94da5 --- /dev/null +++ b/orchestrator/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@server/*": ["src/server/*"], + "@client/*": ["src/client/*"], + "@shared/*": ["src/shared/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/orchestrator/tsconfig.server.json b/orchestrator/tsconfig.server.json new file mode 100644 index 0000000..0f600b8 --- /dev/null +++ b/orchestrator/tsconfig.server.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "outDir": "./dist/server", + "rootDir": "./src/server" + }, + "include": ["src/server/**/*", "src/shared/**/*"] +} diff --git a/orchestrator/vite.config.ts b/orchestrator/vite.config.ts new file mode 100644 index 0000000..a7efc16 --- /dev/null +++ b/orchestrator/vite.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + '@client': path.resolve(__dirname, './src/client'), + '@shared': path.resolve(__dirname, './src/shared'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true, + }, + }, + }, + build: { + outDir: 'dist/client', + emptyOutDir: true, + }, +}); diff --git a/resume-generator/rxresume_automation.py b/resume-generator/rxresume_automation.py index 3b12560..6d52170 100644 --- a/resume-generator/rxresume_automation.py +++ b/resume-generator/rxresume_automation.py @@ -7,8 +7,8 @@ from pathlib import Path from playwright.sync_api import sync_playwright # Configuration -RXRESUME_EMAIL = os.getenv("RXRESUME_EMAIL", "ssarfaraz@lancashire.ac.uk") -RXRESUME_PASSWORD = os.getenv("RXRESUME_PASSWORD", "thisisatestpassword") +RXRESUME_EMAIL = os.getenv("RXRESUME_EMAIL", "") +RXRESUME_PASSWORD = os.getenv("RXRESUME_PASSWORD", "") BASE_DIR = Path(__file__).parent RESUME_JSON_PATH = BASE_DIR / "base.json" @@ -79,7 +79,7 @@ def generate_resume_pdf( output_path = OUTPUT_DIR / output_filename with sync_playwright() as playwright: - browser = playwright.chromium.launch(headless=False) + browser = playwright.chromium.launch(headless=True) context = browser.new_context() page = context.new_page() diff --git a/resume-generator/temp_resume_b551b26e-7cf0-4bc5-be69-36d8e813f5b2.json b/resume-generator/temp_resume_b551b26e-7cf0-4bc5-be69-36d8e813f5b2.json new file mode 100644 index 0000000..681171e --- /dev/null +++ b/resume-generator/temp_resume_b551b26e-7cf0-4bc5-be69-36d8e813f5b2.json @@ -0,0 +1,661 @@ +{ + "basics": { + "name": "Shaheer Sarfaraz", + "headline": "Frontend Software Engineer (React/TypeScript) · Autodesk Intern · Open Source & Product Work", + "email": "shaheer30sarfaraz@gmail.com", + "phone": "+44 7359 501592", + "location": "Blackpool, United Kingdom", + "url": { + "label": "https://dakheera47.com/", + "href": "https://dakheera47.com/" + }, + "customFields": [], + "picture": { + "url": "", + "size": 120, + "aspectRatio": 1, + "borderRadius": 0, + "effects": { + "hidden": false, + "border": false, + "grayscale": false + } + } + }, + "sections": { + "summary": { + "name": "Summary", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "summary", + "content": "" + }, + "awards": { + "name": "Awards", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "awards", + "items": [] + }, + "certifications": { + "name": "Certifications", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "certifications", + "items": [] + }, + "education": { + "name": "Education", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "education", + "items": [ + { + "id": "yo3p200zo45c6cdqc6a2vtt3", + "visible": true, + "institution": "University of Lancashire", + "studyType": "BSc (Hons) Computer Science", + "area": "Preston, United Kingdom", + "score": "1st Class", + "date": "September 2022 to June 2026", + "summary": "

Relevant Modules: Web Applications, Algorithms & Data Structures, Game Development, Databases, Software Engineering (Agile group project)

", + "url": { + "label": "", + "href": "https://www.lancashire.ac.uk/undergraduate/courses/computer-science-bsc" + } + }, + { + "id": "ei2fvjokusg3cfmdyolmgcoz", + "visible": false, + "institution": " ", + "studyType": "", + "area": "A Levels", + "score": "", + "date": "", + "summary": "
  • Maths: A

  • Computer Science: B

  • Physics: C

  • Chemistry: E

", + "url": { + "label": "", + "href": "" + } + }, + { + "id": "pm4r5hngvv1w4mc79o22irfx", + "visible": false, + "institution": " ", + "studyType": "", + "area": "GCSEs", + "score": "", + "date": "", + "summary": "
  1. English: A*

  2. Computer Science: A*

  3. Urdu: A

  4. Islamiat: A

  5. Pakistan Studies: A

  6. Biology: A

  7. Chemistry: A

  8. Physics: A

  9. Maths: A

", + "url": { + "label": "", + "href": "" + } + } + ] + }, + "experience": { + "name": "Experience", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "experience", + "items": [ + { + "id": "ng9ui2azk7w4y8oyu8kazqeb", + "visible": true, + "company": "Autodesk", + "position": "Software Engineering Intern", + "location": "Hybrid (Sheffield Based)", + "date": "July 2024 - June 2025", + "summary": "
  • Implemented front-end features and fixes in the Autodesk Construction Cloud Model Coordination app, working in a ~10-year-old React/JavaScript/TypeScript codebase (7k+ commits) using Webpack module federation and Autodesk’s Exoskeleton dev environment

  • Improved reliability of the Cypress end-to-end test suite by diagnosing flaky tests, adding new E2E coverage, and participating in focused “test fest” events ahead of major feature releases

  • Collaborated with cross-functional teams (like the Design System, platform teams) by raising well-scoped bugs, augmenting existing tickets with reproduction steps and context, and aligning on shared component and API changes

  • Helped strengthen team processes by running weekly stand-ups and retrospectives, organising a ticket-scoping meeting, and participating in technical reviews & ADR discussions (e.g. standardising error handling and planning clash data streaming)

", + "url": { + "label": "", + "href": "" + } + }, + { + "id": "lhw25d7gf32wgdfpsktf6e0x", + "visible": true, + "company": "Mirage", + "position": "Co-Founder & Lead Developer", + "location": "", + "date": "December 2019 to Present", + "summary": "
  • Delivered 10+ production websites and webapps for small and medium size clients (e.g. Indus Marine Services, Mumtaz Urdu), from initial scoping to deployment and handover

  • Built with modern web stacks (Next.js, Node/Express, Tailwind, Strapi, WordPress/Elementor where appropriate), setting up CI/CD and hosting

  • Led a small team of four developers, handling code reviews, task breakdown, and client communication

", + "url": { + "label": "", + "href": "https://promirage.com/" + } + }, + { + "id": "k6zxqunkb225hbjso3c3vykk", + "visible": true, + "company": "University of Lancashire", + "position": "Computing Student Mentor", + "location": "Preston, UK", + "date": "July 2023 - July 2024", + "summary": "
  • Academic Support and Leadership: Provided academic guidance to over 10 first-year students once a week, significantly enhancing their understanding and skills in key subjects like programming and web development.

  • Collaborative Learning Environment: Actively fostered a collaborative and supportive learning environment for a group of 10 students. This role also honed my leadership and communication skills, facilitating better academic outcomes for mentees.

", + "url": { + "label": "", + "href": "" + } + }, + { + "id": "a1bg5d8gp8sulf91xzdcsiaq", + "visible": true, + "company": "Research and Knowledge Exchange Institute", + "position": "Undergraduate Research Intern (HCI & EdTech)", + "location": "", + "date": "Summer 2024", + "summary": "
  • Built a mouse “torch-reveal” web app (Astro) to approximate eye-tracking; ran on-campus studies with Revoe Learning Academy pupils—1 eye-tracked, 9 using my app.

  • Logged cursor paths, dwell time, and reveal order; delivered setup notes for staff to run sessions independently.

  • Developed a Questionnaire Randomiser (Next.js): selectable response metrics (smileys / numbers / stars), configurable randomisation strategies, and ZIP export of per-student PDFs ready for print.

  • Extras: lightweight analytics for comparison with the eye-tracking baseline; optional CSV/JSON data export.

", + "url": { + "label": "", + "href": "" + } + }, + { + "id": "tx32suzrg2bs5eumcbjei4ns", + "visible": false, + "company": "University of Lancashire", + "position": "Student Ambassador", + "location": "Preston, UK", + "date": "July 2023 - Present", + "summary": "
  • Diverse Role Engagement: Actively engaged in various tasks, from guiding tours to assisting on open days, demonstrating adaptability and organizational skills.

  • Campus Culture Promotion: Contributed to enhancing the university’s inclusive campus atmosphere, showcasing the university's vibrant community to prospective students.

", + "url": { + "label": "", + "href": "" + } + } + ] + }, + "volunteer": { + "name": "Volunteering", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "volunteer", + "items": [] + }, + "interests": { + "name": "Interests", + "columns": 1, + "separateLinks": true, + "visible": false, + "id": "interests", + "items": [] + }, + "languages": { + "name": "Languages", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "languages", + "items": [] + }, + "profiles": { + "name": "Profiles", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "profiles", + "items": [ + { + "id": "ukl0uecvzkgm27mlye0wazlb", + "visible": true, + "network": "GitHub", + "username": "DaKheera47", + "icon": "github", + "url": { + "label": "", + "href": "https://github.com/DaKheera47" + } + }, + { + "id": "cnbk5f0aeqvhx69ebk7hktwd", + "visible": true, + "network": "LinkedIn", + "username": "ssarfaraz30", + "icon": "linkedin", + "url": { + "label": "", + "href": "https://www.linkedin.com/in/ssarfaraz30/" + } + }, + { + "id": "linnyxv78zdep1xwirpa2ia1", + "visible": true, + "network": "Hashnode", + "username": "DaKheera47", + "icon": "hashnode", + "url": { + "label": "", + "href": "https://dakheera47.hashnode.dev/" + } + } + ] + }, + "projects": { + "name": "Projects", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "projects", + "items": [ + { + "id": "yw843emozcth8s1ubi1ubvlf", + "visible": false, + "name": "Atoro", + "description": "Lead Developer", + "date": "January 2023", + "summary": "
  1. Next.js Implementation for Enhanced SEO: Utilized Next.js to optimize the website for search engines, significantly improving its online visibility and user engagement.

  2. Strapi Backend Integration: Streamlined content management by implementing a Strapi backend, enhancing the efficiency and scalability of the website's content updates.

  3. Responsive Design with Tailwind CSS: Employed Tailwind CSS for a utility-first approach, ensuring the website's responsiveness and seamless user experience across various devices.

  4. Continuous Deployment Pipeline Establishment: Developed a continuous deployment pipeline, ensuring real-time updates and maintaining high performance and reliability of the website.

  5. Optimized Web Performance: Focused on optimizing web performance by efficiently loading images and managing JavaScript bundles, leading to a faster and more efficient user experience.

", + "keywords": [], + "url": { + "label": "", + "href": "https://atoro.promirage.com" + } + }, + { + "id": "ncxgdjjky54gh59iz2t1xi1v", + "visible": false, + "name": "Stellar Consultancy", + "description": "Lead Developer", + "date": "April 2023", + "summary": "
  1. WordPress and Elementor Integration: Expertly utilized WordPress with Elementor to build a robust content management system, enhancing the website's scalability and user interaction capabilities.

  2. Client Engagement and Trust Building: Implemented features to showcase client testimonials, effectively building trust and displaying the success of previous project engagements.

  3. Intuitive Design and User Engagement: Focused on intuitive page design and structuring, streamlining site maintenance and content updates, thereby enhancing user engagement.

  4. Effective Call-to-Actions: Crafted clear call-to-actions and provided essential contact information, significantly improving user interaction and conversion rates.

  5. Portfolio Display for Business Showcase: Presented past work and services offered through a comprehensive portfolio display, allowing visitors to assess the quality and impact of Stellar Consultancy's services.

", + "keywords": [], + "url": { + "label": "", + "href": "https://stellarconsultancy.ca" + } + }, + { + "id": "tcecguinuctb8mu2xqrn97m8", + "visible": true, + "name": "Mumtaz Urdu", + "description": "Developer", + "date": "July 2022", + "summary": "
  1. Server-Rendered Web Application Development: Created the Mumtaz Urdu platform with Next.js to optimize server-side rendering for enhanced SEO and performance.

  2. UI Development with Tailwind CSS: Implemented utility-first Tailwind CSS, ensuring rapid, responsive design for a seamless user interface.

  3. Scalable Storage Solution: Integrated scalable Amazon S3 storage, supporting the application's growth and robust data management.

  4. Progressive Web App Implementation: Developed PWA features for Mumtaz Urdu, offering users native-like mobile access and increased engagement.

  5. High Traffic Data Management: Engineered Mumtaz Urdu's backend with Next.js and MongoDB, enabling the handling and efficient processing of vast amounts of user data for thousands of monthly users.

  6. Test-Driven Development: Embraced TDD practices to ensure reliable and high-quality code, facilitating regular testing throughout the development process for continuous improvement.

", + "keywords": [], + "url": { + "label": "", + "href": "https://www.mumtazurdu.com/" + } + }, + { + "id": "to47h749kaj6t02j3f9kprxq", + "visible": false, + "name": "PyScreeze", + "description": "Open Source Contribution", + "date": "January 2022", + "summary": "
  1. Innovative Feature Implementation: Implemented the locateCenterOnScreenNear function for PyScreeze, enhancing the library's functionality by enabling precise image location near a specified point on the screen.

  2. Open Source Contribution: Marked my debut in open-source contributions with this significant addition to PyScreeze, showcasing my initiative and ability to contribute effectively to community-driven projects.

  3. Collaborative Development and Recognition: Collaborated with the project's maintainer, asweigart, to refine and integrate the function into the main codebase, receiving recognition for this valuable contribution to the project.

", + "keywords": [], + "url": { + "label": "", + "href": "https://github.com/asweigart/pyscreeze/pull/79" + } + }, + { + "id": "gt7yq82ulor5hmmutdhuvfo1", + "visible": false, + "name": "Threegency", + "description": "Lead Developer", + "date": "February 2023", + "summary": "
  • Framework: Utilized Next.js to build a server-rendered React website, enhancing SEO and ensuring optimal performance.

  • Styling: Employed Tailwind CSS for utility-first styling, facilitating rapid UI development.

  • Content Management: Leveraged Strapi as a CMS, enabling streamlined content updates and administration.

  • Data Handling: Utilized GraphQL for data handling, ensuring efficient and flexible data retrieval.

", + "keywords": [], + "url": { + "label": "", + "href": "https://www.threegency.com" + } + }, + { + "id": "c8fcu3nz541a4d5zcurx6b8c", + "visible": false, + "name": "AutoClass", + "description": "GUI Automation", + "date": "November 2021", + "summary": "
  • Framework: Written in Python, leveraging the versatility and ease-of-use of the language.

  • Automation Library: Utilized PyAutoGUI for automating user interactions, enhancing the utility of the application.

  • Iterative Improvement: Progressively refined over a year, demonstrating a commitment to robustness and reliability.

  • Project Purpose: Developed to automate the process of joining Zoom classes, alleviating the repetitive morning routine.

", + "keywords": [], + "url": { + "label": "", + "href": "https://github.com/DaKheera47/autoclass" + } + }, + { + "id": "rv23bgibq6bye6rujmcx1ygc", + "visible": false, + "name": "Meet Link Generator", + "description": "GUI Automation", + "date": "January 2022", + "summary": "
  • Functionality: Generates Google Meet links with specific words in the URL by brute-forcing the creation of thousands of links until the desired pattern is achieved. Doing so enables creation of Google Meet links with specific codes or phrases.

  • Optimized Automation: The final product uses Python with PyAutoGUI for efficient and rapid creation of new Google Meet links.

  • Speed and Efficiency: Drastically improved performance, finally achieving the link generation time to under 1 second per link, limited only by internet speed.

  • Interface Interaction: Utilizes the Google Meet homepage's features for quicker link generation, avoiding full page refreshes for speed.

", + "keywords": [], + "url": { + "label": "", + "href": "https://github.com/DaKheera47/meet-link-generator" + } + }, + { + "id": "tu98rghbi5c43ogget5mh7ih", + "visible": false, + "name": "UCLan Server-side Web Application Project", + "description": "", + "date": "UCLan Year 1", + "summary": "
  • Backend Development with PHP and MySQL: Developed the backend for a Student’s Union Shop web application, integrating PHP and MySQL for dynamic data handling and backend database communication.

  • User Authentication and Session Management: Implemented user sign-up and login functionality using PHP sessions, enabling secure and personalized shopping experiences.

  • Dynamic Content Display from Database: Enhanced the application to dynamically display products and offers directly from the database, moving away from static HTML content.

  • Advanced Search and Personalization Features: Integrated advanced product search capabilities and personalized user greetings, improving user interactivity and engagement.

", + "keywords": [], + "url": { + "label": "", + "href": "" + } + }, + { + "id": "ov4lkbc1vl169ynfnj91m1lm", + "visible": false, + "name": "Square About", + "description": "", + "date": "UCLan Year 1", + "summary": "
  • Advanced 3D Game Development: Implemented a complex 3D game using TL-Engine, featuring intricate gameplay mechanics and immersive 3D visuals.

  • Dynamic Gameplay Elements: Integrated multiple spheres with varying behaviors, including super-spheres requiring multiple hits, enhancing the game's challenge and engagement levels.

  • Interactive Game Controls: Developed features for speed control and directional change, allowing players to interact dynamically with the game environment.

  • Strategic Game Mechanics: Added a bullet firing mechanism with a limited ammo concept, introducing strategic elements and a scoring system to the gameplay.

", + "keywords": [], + "url": { + "label": "", + "href": "" + } + }, + { + "id": "s3r37gdr0oa84a6dp6r5nl58", + "visible": false, + "name": "Car Smash", + "description": "", + "date": "UCLan Year 1", + "summary": "
  1. 3D Car Smash Game Development: Developed a 3D car smash game using TL-Engine, showcasing skills in game engine utilization and 3D gaming.

  2. Collision Detection Mechanics: Implemented advanced collision detection between player's car and enemy vehicles, enhancing gameplay realism.

  3. Dynamic Game States and Camera Views: Integrated multiple game states and camera views, including a chase camera and first-person view, for an immersive gaming experience.

  4. Enhanced Player Interaction: Created a more realistic driving experience with accelerated movement and bounce effects on collisions, and introduced particle systems for visual effects.

", + "keywords": [], + "url": { + "label": "", + "href": "" + } + }, + { + "id": "gylzkvl103m9s7ywag4xpdy4", + "visible": false, + "name": "Tweet Filter", + "description": "", + "date": "UCLan Year 1", + "summary": "
  1. Tweet Filtration System: Crafted a C++ program to filter out prohibited words from tweets, showcasing text processing and file handling capabilities.

  2. Advanced Text Manipulation: Enhanced the program to filter varying cases and contexts of banned words, even within larger strings, demonstrating attention to detail in string operations.

  3. Output Generation: Implemented functionality to write filtered tweets to new files, maintaining data integrity and displaying proficiency in file I/O operations.

  4. Algorithm Optimization: Utilized data structures like vectors and implemented mathematical techniques for efficient word frequency analysis and sentiment determination.

", + "keywords": [], + "url": { + "label": "", + "href": "" + } + }, + { + "id": "enav754zxhuc9uycbb83s94q", + "visible": false, + "name": "Burger Ordering App", + "description": "", + "date": "UCLan Year 1", + "summary": "
  1. Interactive Console Application: Engineered a C++ console application simulating a burger ordering process, highlighting proficiency in creating user-interactive software.

  2. Complex Logic Implementation: Designed and implemented complex logic for burger size and topping selection, including pricing and order summary features.

  3. Data Handling and User Input: Developed robust credit system and user input validation for an intuitive ordering experience, showcasing attention to detail and user-centric design.

  4. Readable and Maintainable Code: Produced well-documented, maintainable code with clear variable naming and structured formatting, demonstrating best practices in software development.

", + "keywords": [], + "url": { + "label": "", + "href": "" + } + }, + { + "id": "hl6jgeswr01tlul3iwoat05d", + "visible": false, + "name": "LinkLander", + "description": "Android Studio, Kotlin", + "date": "December 2023 - Ongoing", + "summary": "
  • Innovative Android Utility: Developed LinkLander, a Kotlin-based Android application that simplifies the process of downloading online content directly to devices.

  • User-Centric Design: Focused on addressing Android system limitations by providing a seamless shortcut for redirecting links to an online video downloading service.

  • Simplicity and Efficiency: Emphasized a user-friendly interface, enhancing the Android experience by streamlining content downloads.

  • Technical Proficiency in Kotlin: Leveraged the capabilities of Kotlin for Android development to create a practical solution for niche digital tasks.

", + "keywords": [], + "url": { + "label": "", + "href": "" + } + }, + { + "id": "v4s0ljbiiio198y8l1wl0ym6", + "visible": false, + "name": "AR App Development with AGILE", + "description": "Unity, C#", + "date": "October 2023 - Ongoing", + "summary": "
  • Agile Development in Action: Participated in an Agile team project, developing an AR application for supporting disabled students with a team of five, demonstrating an application of Agile methodologies in a real-world scenario.

  • Mobile AR Application Prototype: Developed a proof-of-concept prototype using Unity and C# for mobile platforms, showcasing technical skills in modern app development environments.

  • Collaborative Software Engineering: Engaged in a collaborative environment, contributing code and ideas, emphasizing teamwork and shared responsibility in software creation.

  • Presentation and Critical Analysis: Delivered a comprehensive presentation and critical report, evaluating the Agile process, product development, and personal learning outcomes.

", + "keywords": [], + "url": { + "label": "", + "href": "" + } + }, + { + "id": "fwxrq682hqrj1y76rmziqrbk", + "visible": true, + "name": "Indus Marine Services", + "description": "System Design & Development", + "date": "May 2022 - Ongoing", + "summary": "
  1. Induction System for Marine Services: Designed & developed an induction system for Indus Marine Services in the UAE, streamlining the employee onboarding process with interactive testing and certification issuance.

  2. Admin-Centric Functionality: Devised a back-end system allowing admins to oversee inductee progress, manage documents, and curate customized quizzes as per requirements

  3. Client Engagement Interface: Implemented a user-friendly front-end where inductees receive personalized email prompts, complete quizzes, and obtain certifications, all contributing to a seamless induction experience.

  4. Robust Tech Stack Integration: Utilized a sophisticated stack comprising Node.js, Express, EJS, and Tailwind CSS to build a responsive, scalable, and easily navigable system.

", + "keywords": [], + "url": { + "label": "", + "href": "http://www.ims-auh.com" + } + }, + { + "id": "jdfyaez8vq1b7xfr9rmxmz06", + "visible": false, + "name": "VECTOR AI", + "description": "Website Development", + "date": "February 2024 - February 2024", + "summary": "
  1. Innovative AI Development: As the driving force behind VECTOR's website development, I spearheaded the technical design using Astro, with a cutting-edge stack including React and Tailwind CSS.

  2. Data-Driven Content Strategy: Leveraged Astro content management capabilities to structure and present data, ensuring content is dynamic, easily accessible, and optimized for both performance and scalability.

  3. Astro for Enhanced Performance: Utilized Astro for static site generation, making VECTOR's website performance fast for a pleasant user experience

  4. React for Responsive Interaction: Utilized React’s robust ecosystem to develop interactive elements, ensuring that each module of VECTOR’s platform is engaging and seamless for users across various touchpoints.

", + "keywords": [], + "url": { + "label": "", + "href": "https://vector-ai.co/" + } + }, + { + "id": "qdhmfkqpfql19ohfas1g91ek", + "visible": false, + "name": "UCLan's First Hackathon", + "description": "Hackathon, Team Work", + "date": "February 2024", + "summary": "
  1. Second Place in UCLan Hackathon: Earned second place in UCLan's first hackathon by developing an app to simplify university life. Focused on enhancing the attendance monitoring process for student mentors.

  2. TRPC for End-to-End Type Safety: Utilized TRPC to ensure end-to-end type safety, enhancing the app's reliability and streamlining the development process.

  3. Supabase Backend Integration: Implemented Supabase as a backend solution, providing a robust and scalable database for managing attendance data efficiently.

  4. Amazon SES and OAuth Integration: Integrated Amazon SES for email notifications and OAuth for secure Google login, improving user experience and communication.

", + "keywords": [], + "url": { + "label": "", + "href": "" + } + }, + { + "id": "rw3x7tapntrt877rbl4pnxz7", + "visible": true, + "name": "NASA Space Apps Challenge", + "description": "A 48-hour, global hackathon powered by NASA open data", + "date": "Oct 4–5, 2025", + "summary": "
  1. Full-Stack Integration: Wired up backend services to a responsive frontend, enabling real-time exploration of Kepler/K2/TESS catalogs and smooth model-scoring UX.

  2. Data Harmonization Pipeline: Cleaned, merged, and standardized multi-mission catalogs into a unified schema, unblocking ML teammates and cutting data-prep time by 60%+ during the hack.

  3. Analytics UI & Upload Flow: Built an upload → validate → score workflow and a clear results dashboard so researchers can triage candidates in minutes, not hours.

  4. Delivery Under Pressure: Coordinated a 5-person multidisciplinary team to ship a working web app in 48 hours, with demo-ready reliability for judging.

", + "keywords": [], + "url": { + "label": "", + "href": "https://exploranium.vercel.app/dashboard" + } + }, + { + "id": "i2t6epmx5v7s0d8rqtxsigp3", + "visible": true, + "name": "Strong Statistics", + "description": "Self-hosted strength analytics app using FastAPI and Next.js to visualize Strong app data with full local privacy and active open-source adoption.", + "date": "September 2025 - Present", + "summary": "
  1. Self-Hosted Strength Analytics Platform: Developed strong-statistics, an open-source web app that visualizes detailed workout analytics from the Strong and Hevy fitness app, giving users local control of their training data.

  2. Full-Stack Architecture: Built a modular stack with FastAPI, Next.js, Tailwind CSS, and SQLite, deployed via Docker Compose for seamless self-hosting and persistent local data storage.

  3. Active Open-Source Ecosystem: Published on GitHub with community engagement from global users — external contributors opened feature requests and bug reports, validating real-world adoption and reliability.

  4. Continuous Personal Use & Maintenance: Regularly updated and used in live deployment at lifting.dakheera47.com, tracking hundreds of sets over time with persistent analytics and performance trends.

", + "keywords": [], + "url": { + "label": "", + "href": "https://lifting.dakheera47.com/" + } + } + ] + }, + "publications": { + "name": "Publications", + "columns": 1, + "separateLinks": true, + "visible": true, + "id": "publications", + "items": [] + }, + "references": { + "name": "References", + "columns": 1, + "separateLinks": true, + "visible": false, + "id": "references", + "items": [ + { + "id": "f2sv5z0cce6ztjl87yuk8fak", + "visible": true, + "name": "Available upon request", + "description": "", + "summary": "", + "url": { + "label": "", + "href": "" + } + } + ] + }, + "skills": { + "name": "Skills", + "columns": 2, + "separateLinks": true, + "visible": true, + "id": "skills", + "items": [ + { + "id": "jfgzfcwcg65k9gemuxlfe9m3", + "visible": true, + "name": "Frontend Development", + "description": "", + "level": 0, + "keywords": [ + "React", + "Next.js", + "Tailwind CSS", + "Strapi CMS", + "Elementor", + "GraphQL", + "TypeScript", + "CI/CD", + "PWA Development", + "AstroJS", + "React Testing Library" + ] + }, + { + "id": "sk3957foopxir2hw4xzxqahh", + "visible": true, + "name": "Backend Development", + "description": "", + "level": 0, + "keywords": [ + "Node.js", + "Express.js", + "MongoDB", + "Supabase", + "Firebase", + "Docker", + "FastAPI", + "AWS S3", + "AWS SES" + ] + }, + { + "id": "d9bddwdj6qreknhk644rm0bs", + "visible": true, + "name": "Leadership and Problem-Solving", + "description": "", + "level": 0, + "keywords": [ + "Agile Project Management", + "Conflict Resolution", + "Creative Problem-Solving", + "Decision-Making", + "Effective Communication", + "Adaptability" + ] + }, + { + "id": "gk4hrky0wnbsbdcmmud48zjh", + "visible": true, + "name": "Other Programming", + "description": "", + "level": 0, + "keywords": [ + "Python Scripting", + "PyAutoGUI", + "Git", + "GitHub", + "Selenium", + "Data Analysis", + "Web Scraping", + "Data Cleaning" + ] + } + ] + }, + "custom": {} + }, + "metadata": { + "template": "onyx", + "layout": [ + [ + [ + "summary", + "education", + "experience", + "projects", + "references" + ], + [ + "profiles", + "skills", + "certifications", + "interests", + "languages", + "awards", + "volunteer", + "publications" + ] + ] + ], + "css": { + "value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}", + "visible": false + }, + "page": { + "margin": 34, + "format": "a4", + "options": { + "breakLine": false, + "pageNumbers": false + } + }, + "theme": { + "background": "#ffffff", + "text": "#000000", + "primary": "#475569" + }, + "typography": { + "font": { + "family": "IBM Plex Sans", + "subset": "latin", + "variants": [ + "regular" + ], + "size": 13 + }, + "lineHeight": 1.75, + "hideIcons": false, + "underlineLinks": true + }, + "notes": "" + } +} \ No newline at end of file