From 234314d1c6ba448987c895d104d15ca63ca55338 Mon Sep 17 00:00:00 2001 From: Marcio Bevervanso Date: Sun, 7 Jun 2026 19:30:33 -0300 Subject: [PATCH] Publish MetaAds Pro: AI-driven Meta Ads campaign management dashboard Full app with AI campaign creation/copy/creative generation, dashboard, campaign automation engine, Meta OAuth login, and a redesigned landing page. Co-Authored-By: Claude Sonnet 4.6 --- .claude/scheduled_tasks.lock | 1 + .gitignore | 6 + mcp-config.json | 12 + mcp-server/index.ts | 243 ++ mcp-server/package.json | 19 + mcp-server/tsconfig.json | 17 + n8n-workflow.json | 93 + package-lock.json | 3249 +++++++++++++++++++- package.json | 12 +- src/app/api/ai/analyze-link/route.ts | 29 + src/app/api/ai/generate-audience/route.ts | 29 + src/app/api/ai/generate-copy/route.ts | 30 + src/app/api/ai/generate-image/route.ts | 41 + src/app/api/analysis/chat/route.ts | 53 + src/app/api/analysis/route.ts | 144 + src/app/api/auth/callback/route.ts | 81 + src/app/api/auth/logout/route.ts | 13 + src/app/api/auth/meta/route.ts | 37 + src/app/api/campaigns/[id]/budget/route.ts | 45 + src/app/api/campaigns/[id]/status/route.ts | 50 + src/app/api/campaigns/create/route.ts | 270 ++ src/app/api/campaigns/draft/[id]/route.ts | 24 + src/app/api/campaigns/draft/route.ts | 93 + src/app/api/campaigns/insights/route.ts | 53 + src/app/api/campaigns/route.ts | 87 + src/app/api/cron/monitor/route.ts | 70 + src/app/api/debug/route.ts | 15 + src/app/api/engine/rules/route.ts | 67 + src/app/api/engine/run/route.ts | 44 + src/app/api/meta/pages/route.ts | 43 + src/app/api/settings/route.ts | 24 + src/app/dashboard/activity/page.tsx | 151 + src/app/dashboard/analysis/page.tsx | 462 +++ src/app/dashboard/automation/page.tsx | 261 ++ src/app/dashboard/create/page.tsx | 1438 +++++++++ src/app/dashboard/history/page.tsx | 321 ++ src/app/dashboard/layout.tsx | 15 + src/app/dashboard/page.tsx | 220 ++ src/app/dashboard/settings/page.tsx | 197 ++ src/app/data-deletion/page.tsx | 65 + src/app/globals.css | 510 ++- src/app/layout.tsx | 29 +- src/app/login/page.tsx | 96 + src/app/page.tsx | 262 +- src/app/privacy/page.tsx | 85 + src/app/setup/page.tsx | 34 + src/app/terms/page.tsx | 84 + src/components/dashboard/BudgetModal.tsx | 108 + src/components/dashboard/CampaignTable.tsx | 265 ++ src/components/dashboard/DateFilter.tsx | 40 + src/components/dashboard/MetricsCard.tsx | 142 + src/components/dashboard/Sidebar.tsx | 133 + src/components/public/AnimatedCounter.tsx | 39 + src/components/public/FloatingOrbs.tsx | 37 + src/components/public/HowItWorks.tsx | 130 + src/components/public/LegalPage.tsx | 36 + src/components/public/LiveChartMock.tsx | 130 + src/components/public/PublicFooter.tsx | 19 + src/components/public/PublicHeader.tsx | 35 + src/components/public/ScrollReveal.tsx | 28 + src/components/public/StatsStrip.tsx | 30 + src/components/theme/ThemeProvider.tsx | 56 + src/components/theme/ThemeToggle.tsx | 27 + src/components/ui/ErrorBlock.tsx | 60 + src/components/ui/Modal.tsx | 34 + src/components/ui/Spinner.tsx | 44 + src/config/skins/index.ts | 208 ++ src/lib/ai/analyzer.ts | 121 + src/lib/ai/client.ts | 26 + src/lib/ai/copywriter.ts | 123 + src/lib/ai/creative.ts | 65 + src/lib/ai/scraper.ts | 68 + src/lib/ai/strategist.ts | 98 + src/lib/auth/session.ts | 50 + src/lib/engine/monitor.ts | 254 ++ src/lib/engine/rules.ts | 132 + src/lib/errors.ts | 89 + src/lib/mcp/client.ts | 357 +++ src/lib/mcp/tools.ts | 615 ++++ src/lib/meta/create.ts | 8 + src/lib/meta/targeting.ts | 169 + src/lib/meta/types.ts | 126 + src/lib/settings.ts | 34 + src/lib/supabase/server.ts | 20 + src/middleware.ts | 31 + supabase/migration.sql | 78 + tsconfig.json | 2 +- 87 files changed, 13166 insertions(+), 125 deletions(-) create mode 100644 .claude/scheduled_tasks.lock create mode 100644 mcp-config.json create mode 100644 mcp-server/index.ts create mode 100644 mcp-server/package.json create mode 100644 mcp-server/tsconfig.json create mode 100644 n8n-workflow.json create mode 100644 src/app/api/ai/analyze-link/route.ts create mode 100644 src/app/api/ai/generate-audience/route.ts create mode 100644 src/app/api/ai/generate-copy/route.ts create mode 100644 src/app/api/ai/generate-image/route.ts create mode 100644 src/app/api/analysis/chat/route.ts create mode 100644 src/app/api/analysis/route.ts create mode 100644 src/app/api/auth/callback/route.ts create mode 100644 src/app/api/auth/logout/route.ts create mode 100644 src/app/api/auth/meta/route.ts create mode 100644 src/app/api/campaigns/[id]/budget/route.ts create mode 100644 src/app/api/campaigns/[id]/status/route.ts create mode 100644 src/app/api/campaigns/create/route.ts create mode 100644 src/app/api/campaigns/draft/[id]/route.ts create mode 100644 src/app/api/campaigns/draft/route.ts create mode 100644 src/app/api/campaigns/insights/route.ts create mode 100644 src/app/api/campaigns/route.ts create mode 100644 src/app/api/cron/monitor/route.ts create mode 100644 src/app/api/debug/route.ts create mode 100644 src/app/api/engine/rules/route.ts create mode 100644 src/app/api/engine/run/route.ts create mode 100644 src/app/api/meta/pages/route.ts create mode 100644 src/app/api/settings/route.ts create mode 100644 src/app/dashboard/activity/page.tsx create mode 100644 src/app/dashboard/analysis/page.tsx create mode 100644 src/app/dashboard/automation/page.tsx create mode 100644 src/app/dashboard/create/page.tsx create mode 100644 src/app/dashboard/history/page.tsx create mode 100644 src/app/dashboard/layout.tsx create mode 100644 src/app/dashboard/page.tsx create mode 100644 src/app/dashboard/settings/page.tsx create mode 100644 src/app/data-deletion/page.tsx create mode 100644 src/app/login/page.tsx create mode 100644 src/app/privacy/page.tsx create mode 100644 src/app/setup/page.tsx create mode 100644 src/app/terms/page.tsx create mode 100644 src/components/dashboard/BudgetModal.tsx create mode 100644 src/components/dashboard/CampaignTable.tsx create mode 100644 src/components/dashboard/DateFilter.tsx create mode 100644 src/components/dashboard/MetricsCard.tsx create mode 100644 src/components/dashboard/Sidebar.tsx create mode 100644 src/components/public/AnimatedCounter.tsx create mode 100644 src/components/public/FloatingOrbs.tsx create mode 100644 src/components/public/HowItWorks.tsx create mode 100644 src/components/public/LegalPage.tsx create mode 100644 src/components/public/LiveChartMock.tsx create mode 100644 src/components/public/PublicFooter.tsx create mode 100644 src/components/public/PublicHeader.tsx create mode 100644 src/components/public/ScrollReveal.tsx create mode 100644 src/components/public/StatsStrip.tsx create mode 100644 src/components/theme/ThemeProvider.tsx create mode 100644 src/components/theme/ThemeToggle.tsx create mode 100644 src/components/ui/ErrorBlock.tsx create mode 100644 src/components/ui/Modal.tsx create mode 100644 src/components/ui/Spinner.tsx create mode 100644 src/config/skins/index.ts create mode 100644 src/lib/ai/analyzer.ts create mode 100644 src/lib/ai/client.ts create mode 100644 src/lib/ai/copywriter.ts create mode 100644 src/lib/ai/creative.ts create mode 100644 src/lib/ai/scraper.ts create mode 100644 src/lib/ai/strategist.ts create mode 100644 src/lib/auth/session.ts create mode 100644 src/lib/engine/monitor.ts create mode 100644 src/lib/engine/rules.ts create mode 100644 src/lib/errors.ts create mode 100644 src/lib/mcp/client.ts create mode 100644 src/lib/mcp/tools.ts create mode 100644 src/lib/meta/create.ts create mode 100644 src/lib/meta/targeting.ts create mode 100644 src/lib/meta/types.ts create mode 100644 src/lib/settings.ts create mode 100644 src/lib/supabase/server.ts create mode 100644 src/middleware.ts create mode 100644 supabase/migration.sql diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000..e02415e --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"0c60e074-c6b2-4343-b24e-0fb9b7558d41","pid":2474,"procStart":"Sun Jun 7 11:38:33 2026","acquiredAt":1780834101623} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ef6a52..2836c46 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,12 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +# runtime settings — contains real Meta App ID/Secret in plaintext +/settings.json + +# local scratch files +/Arquivo.zip + # vercel .vercel diff --git a/mcp-config.json b/mcp-config.json new file mode 100644 index 0000000..14c7171 --- /dev/null +++ b/mcp-config.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "metaads-pro": { + "command": "node", + "args": ["./mcp-server/dist/index.js"], + "env": { + "META_ACCESS_TOKEN": "YOUR_LONG_LIVED_TOKEN", + "META_AD_ACCOUNT_ID": "act_XXXXXXXXX" + } + } + } +} diff --git a/mcp-server/index.ts b/mcp-server/index.ts new file mode 100644 index 0000000..e2b0f7f --- /dev/null +++ b/mcp-server/index.ts @@ -0,0 +1,243 @@ +#!/usr/bin/env node + +/** + * MetaAds Pro — MCP Server + * + * Exposes Meta Marketing API operations as MCP tools. + * Can be connected to Claude Desktop, Cursor, or any MCP client. + * + * Tools: + * - list_campaigns: Lists all active/paused campaigns + * - get_campaign_insights: Gets metrics for a specific campaign + * - get_account_overview: Gets account-level metrics summary + * - pause_campaign: Pauses a campaign + * - resume_campaign: Resumes a paused campaign + * - update_budget: Updates a campaign's daily budget + */ + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; + +// @ts-expect-error - CJS module +const bizSdk = await import('facebook-nodejs-business-sdk'); + +const accessToken = process.env.META_ACCESS_TOKEN; +const adAccountId = process.env.META_AD_ACCOUNT_ID; + +if (!accessToken || !adAccountId) { + console.error('Error: META_ACCESS_TOKEN and META_AD_ACCOUNT_ID must be set'); + process.exit(1); +} + +// Init Meta API +Object.defineProperty(bizSdk.default.FacebookAdsApi, 'VERSION', { get: () => 'v20.0' }); +bizSdk.default.FacebookAdsApi.init(accessToken); +const AdAccount = bizSdk.default.AdAccount; +const Campaign = bizSdk.default.Campaign; + +// ---- MCP Server ---- +const server = new Server( + { name: 'metaads-pro', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +// ---- Tool Definitions ---- +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'list_campaigns', + description: 'Lista todas as campanhas ativas e pausadas da conta de anúncios do Meta/Facebook', + inputSchema: { + type: 'object' as const, + properties: {}, + required: [], + }, + }, + { + name: 'get_campaign_insights', + description: 'Busca métricas detalhadas (spend, CTR, CPC, CPM, leads, ROAS) de uma campanha específica', + inputSchema: { + type: 'object' as const, + properties: { + campaign_id: { type: 'string', description: 'ID da campanha' }, + date_preset: { + type: 'string', + enum: ['today', 'yesterday', 'last_7d', 'last_14d', 'last_30d', 'this_month', 'lifetime'], + description: 'Período de tempo (padrão: last_30d)', + }, + }, + required: ['campaign_id'], + }, + }, + { + name: 'get_account_overview', + description: 'Retorna um resumo geral da conta: gasto total, leads, ROAS, CTR, CPC, CPM', + inputSchema: { + type: 'object' as const, + properties: { + date_preset: { + type: 'string', + enum: ['today', 'yesterday', 'last_7d', 'last_14d', 'last_30d', 'this_month', 'lifetime'], + description: 'Período de tempo (padrão: last_30d)', + }, + }, + required: [], + }, + }, + { + name: 'pause_campaign', + description: 'Pausa uma campanha ativa', + inputSchema: { + type: 'object' as const, + properties: { + campaign_id: { type: 'string', description: 'ID da campanha para pausar' }, + }, + required: ['campaign_id'], + }, + }, + { + name: 'resume_campaign', + description: 'Reativa uma campanha pausada', + inputSchema: { + type: 'object' as const, + properties: { + campaign_id: { type: 'string', description: 'ID da campanha para reativar' }, + }, + required: ['campaign_id'], + }, + }, + { + name: 'update_budget', + description: 'Altera o orçamento diário de uma campanha (valor em reais)', + inputSchema: { + type: 'object' as const, + properties: { + campaign_id: { type: 'string', description: 'ID da campanha' }, + daily_budget_brl: { type: 'number', description: 'Novo orçamento diário em R$ (ex: 50.00)' }, + }, + required: ['campaign_id', 'daily_budget_brl'], + }, + }, + ], +})); + +// ---- Tool Handlers ---- +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case 'list_campaigns': { + const account = new AdAccount(adAccountId); + const campaigns = await account.getCampaigns( + ['id', 'name', 'status', 'objective', 'daily_budget', 'created_time'], + { limit: 100, filtering: [{ field: 'status', operator: 'IN', value: ['ACTIVE', 'PAUSED'] }] } + ); + const data = campaigns.map((c: Record) => ({ + id: c.id, + name: c.name, + status: c.status, + objective: c.objective, + daily_budget: `R$ ${(parseInt(String(c.daily_budget || '0'), 10) / 100).toFixed(2)}`, + })); + return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }; + } + + case 'get_campaign_insights': { + const { campaign_id, date_preset = 'last_30d' } = args as { campaign_id: string; date_preset?: string }; + const campaign = new Campaign(campaign_id); + const insights = await campaign.getInsights( + ['campaign_name', 'spend', 'ctr', 'cpc', 'cpm', 'impressions', 'clicks', 'actions', 'purchase_roas'], + { date_preset, limit: 1 } + ); + if (!insights || insights.length === 0) { + return { content: [{ type: 'text', text: 'Nenhum dado de insights encontrado para este período.' }] }; + } + const i = insights[0]; + const leads = i.actions?.find((a: { action_type: string }) => a.action_type === 'lead')?.value || '0'; + const roas = i.purchase_roas?.[0]?.value || '0'; + const summary = { + campaign: i.campaign_name, + spend: `R$ ${parseFloat(i.spend || '0').toFixed(2)}`, + impressions: parseInt(i.impressions || '0', 10), + clicks: parseInt(i.clicks || '0', 10), + ctr: `${parseFloat(i.ctr || '0').toFixed(2)}%`, + cpc: `R$ ${parseFloat(i.cpc || '0').toFixed(2)}`, + cpm: `R$ ${parseFloat(i.cpm || '0').toFixed(2)}`, + leads: parseInt(leads, 10), + roas: parseFloat(roas).toFixed(2) + 'x', + }; + return { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] }; + } + + case 'get_account_overview': { + const { date_preset = 'last_30d' } = args as { date_preset?: string }; + const account = new AdAccount(adAccountId); + const insights = await account.getInsights( + ['spend', 'ctr', 'cpc', 'cpm', 'impressions', 'clicks', 'actions', 'purchase_roas'], + { date_preset, level: 'account', limit: 1 } + ); + if (!insights || insights.length === 0) { + return { content: [{ type: 'text', text: 'Nenhum dado disponível para este período.' }] }; + } + const i = insights[0]; + const leads = i.actions?.find((a: { action_type: string }) => a.action_type === 'lead')?.value || '0'; + const roas = i.purchase_roas?.[0]?.value || '0'; + const summary = { + total_spend: `R$ ${parseFloat(i.spend || '0').toFixed(2)}`, + impressions: parseInt(i.impressions || '0', 10), + clicks: parseInt(i.clicks || '0', 10), + ctr: `${parseFloat(i.ctr || '0').toFixed(2)}%`, + cpc: `R$ ${parseFloat(i.cpc || '0').toFixed(2)}`, + cpm: `R$ ${parseFloat(i.cpm || '0').toFixed(2)}`, + leads: parseInt(leads, 10), + roas: parseFloat(roas).toFixed(2) + 'x', + }; + return { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] }; + } + + case 'pause_campaign': { + const { campaign_id } = args as { campaign_id: string }; + const campaign = new Campaign(campaign_id); + await campaign.update([], { [Campaign.Fields.status]: 'PAUSED' }); + return { content: [{ type: 'text', text: `✅ Campanha ${campaign_id} pausada com sucesso.` }] }; + } + + case 'resume_campaign': { + const { campaign_id } = args as { campaign_id: string }; + const campaign = new Campaign(campaign_id); + await campaign.update([], { [Campaign.Fields.status]: 'ACTIVE' }); + return { content: [{ type: 'text', text: `✅ Campanha ${campaign_id} reativada com sucesso.` }] }; + } + + case 'update_budget': { + const { campaign_id, daily_budget_brl } = args as { campaign_id: string; daily_budget_brl: number }; + const budgetCents = Math.round(daily_budget_brl * 100); + const campaign = new Campaign(campaign_id); + await campaign.update([], { [Campaign.Fields.daily_budget]: budgetCents }); + return { + content: [{ type: 'text', text: `✅ Orçamento da campanha ${campaign_id} atualizado para R$ ${daily_budget_brl.toFixed(2)}.` }], + }; + } + + default: + return { content: [{ type: 'text', text: `Tool "${name}" não encontrada.` }], isError: true }; + } + } catch (error) { + const msg = error instanceof Error ? error.message : 'Unknown error'; + return { content: [{ type: 'text', text: `❌ Erro: ${msg}` }], isError: true }; + } +}); + +// ---- Start Server ---- +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('MetaAds Pro MCP Server running on stdio'); +} + +main().catch(console.error); diff --git a/mcp-server/package.json b/mcp-server/package.json new file mode 100644 index 0000000..3ca71ad --- /dev/null +++ b/mcp-server/package.json @@ -0,0 +1,19 @@ +{ + "name": "metaads-pro-mcp", + "version": "1.0.0", + "description": "MCP Server for Meta Ads Management", + "type": "module", + "main": "index.js", + "scripts": { + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "facebook-nodejs-business-sdk": "^24.0.1" + }, + "devDependencies": { + "typescript": "^5.0.0", + "@types/node": "^20.0.0" + } +} diff --git a/mcp-server/tsconfig.json b/mcp-server/tsconfig.json new file mode 100644 index 0000000..6ae5f31 --- /dev/null +++ b/mcp-server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "resolveJsonModule": true + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/n8n-workflow.json b/n8n-workflow.json new file mode 100644 index 0000000..18292e9 --- /dev/null +++ b/n8n-workflow.json @@ -0,0 +1,93 @@ +{ + "name": "Gerador de Imagens (GPT-Image-2)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "generate-ad-image", + "responseMode": "lastNode", + "options": {} + }, + "id": "webhook-node-1", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 250, + 300 + ], + "webhookId": "ad-image-generator" + }, + { + "parameters": { + "method": "POST", + "url": "https://api.openai.com/v1/images/generations", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "Bearer sk-proj-COLOQUE_SUA_CHAVE_AQUI" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"model\": \"gpt-image-2\",\n \"prompt\": \"{{ $json.body.prompt }}\",\n \"n\": 1,\n \"size\": \"{{ $json.body.dimensions || '1024x1024' }}\"\n}", + "options": { + "timeout": 120000 + } + }, + "id": "http-request-1", + "name": "OpenAI - Generate Image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 450, + 300 + ] + }, + { + "parameters": { + "jsCode": "const response = $input.first().json;\n\n// Pega a URL ou o Base64 gerado pela OpenAI\nlet imageUrl = response.data?.[0]?.url;\nif (!imageUrl && response.data?.[0]?.b64_json) {\n imageUrl = `data:image/png;base64,${response.data[0].b64_json}`;\n}\n\n// Devolve o JSON no formato que o Next.js espera\nreturn {\n url: imageUrl,\n revised_prompt: response.data?.[0]?.revised_prompt || \"\"\n};" + }, + "id": "code-node-1", + "name": "Format Output", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 650, + 300 + ] + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "OpenAI - Generate Image", + "type": "main", + "index": 0 + } + ] + ] + }, + "OpenAI - Generate Image": { + "main": [ + [ + { + "node": "Format Output", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": {} +} diff --git a/package-lock.json b/package-lock.json index e618a2e..b51cebb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,19 @@ "name": "metaads-pro", "version": "0.1.0", "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "@supabase/supabase-js": "^2.107.0", + "cheerio": "^1.2.0", + "date-fns": "^4.1.0", + "framer-motion": "^12.40.0", + "lucide-react": "^1.14.0", "next": "16.2.6", + "openai": "^6.37.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "react-markdown": "^10.1.0", + "recharts": "^3.8.1", + "remark-gfm": "^4.0.1" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -453,6 +463,18 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1035,6 +1057,68 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1240,6 +1324,42 @@ "node": ">=12.4.0" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.7", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.7.tgz", + "integrity": "sha512-LFVFtAROHcDy1er5UI6nodRFnZ2SgdCXhfNSI+DpObO8N7Pur/muBGsjzH5wpnFHCYhYVQxZskCkV4koQ//3/Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1247,6 +1367,102 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@supabase/auth-js": { + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.107.0.tgz", + "integrity": "sha512-XA7x+WIeIvuC3GTZ2ey67QcBbGw4n+o5B7M+dMm9KT1lL3wX1B52DfEWW00WuPt/LnniJLLIn1WIm9YPtuxzKQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.107.0.tgz", + "integrity": "sha512-iMtRUmEj1KOgQd/a3MR4hnBlPnZc62DW8+z8aPpnzbxWkexEZUVL2fSgvvp15gqFg1V55e2yMGqgK+yhSQxp5w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", + "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.107.0.tgz", + "integrity": "sha512-7ARs47/tyIjX7T0Ive20d4NY8zQYXsP5/P07jJWxffSIM2gpnSnGRnL/Fe15GPbdjsW2sTYeckHcyaoKbM6yWQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.107.0.tgz", + "integrity": "sha512-cF2KYdR3JIn9YlWGeluY9S0G+otqTdL6hB8GzpatlEIY6fZudCcyFo6Dc3+X9tjeb+x9XcIyNAk9qhNAknjH1A==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.2", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.107.0.tgz", + "integrity": "sha512-/X8OOVwKBn8aVKuHAGOz2yLA0d2OauqhVuy4mNtN+o7wttHOgx1/j+pqOzlsjmhOHrYykF6AJNZhs3gKZzcMUw==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.107.0.tgz", + "integrity": "sha512-ChKzdlWVweMUUhr0U79JhMmgm1haS/C5JquaiCDr70JaGARRtjjoY9rkIheXWybXxTSNzRiQs3Sk8IAg1HS3ZA==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.107.0", + "@supabase/functions-js": "2.107.0", + "@supabase/postgrest-js": "2.107.0", + "@supabase/realtime-js": "2.107.0", + "@supabase/storage-js": "2.107.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1538,13 +1754,102 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1559,6 +1864,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.39", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", @@ -1573,7 +1893,6 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1589,6 +1908,18 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.59.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", @@ -1884,6 +2215,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", @@ -2153,6 +2490,44 @@ "win32" ] }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2193,6 +2568,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2439,6 +2853,16 @@ "node": ">= 0.4" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2458,6 +2882,36 @@ "node": ">=6.0.0" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/brace-expansion": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", @@ -2516,6 +2970,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "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": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -2539,7 +3002,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2553,7 +3015,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2596,6 +3057,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2613,12 +3084,103 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2639,6 +3201,16 @@ "dev": true, "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2646,6 +3218,28 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2653,11 +3247,36 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2668,13 +3287,161 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -2736,11 +3503,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "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" @@ -2754,6 +3530,25 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2797,6 +3592,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2807,6 +3620,19 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -2820,11 +3646,65 @@ "node": ">=0.10.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "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==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2835,6 +3715,12 @@ "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.352", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.352.tgz", @@ -2849,6 +3735,40 @@ "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/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.21.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.1.tgz", @@ -2863,6 +3783,18 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -2936,7 +3868,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2946,7 +3877,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2984,7 +3914,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3040,6 +3969,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3050,6 +3989,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3459,6 +4404,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3469,11 +4424,147 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz", + "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/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/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -3520,6 +4611,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -3556,6 +4663,27 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3610,11 +4738,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/framer-motion": { + "version": "12.40.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz", + "integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.40.0", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3675,7 +4847,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3700,7 +4871,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3788,7 +4958,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3860,7 +5029,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3889,7 +5057,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3898,6 +5065,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -3915,6 +5122,101 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hono": { + "version": "4.12.18", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3925,6 +5227,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3952,6 +5264,18 @@ "node": ">=0.8.19" } }, + "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/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -3967,6 +5291,57 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4125,6 +5500,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4184,6 +5569,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -4237,6 +5632,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4393,7 +5806,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -4424,6 +5836,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4471,6 +5892,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -4835,6 +6262,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4858,6 +6295,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz", + "integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -4868,16 +6314,328 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "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==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4888,6 +6646,569 @@ "node": ">= 8" } }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4925,11 +7246,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion-dom": { + "version": "12.40.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz", + "integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "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==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4973,6 +7308,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "16.2.6", "resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz", @@ -5080,11 +7424,22 @@ "dev": true, "license": "MIT" }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5094,7 +7449,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5203,6 +7557,48 @@ "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/openai": { + "version": "6.37.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.37.0.tgz", + "integrity": "sha512-0H5dEGFmmLv6KSd0W1w2nyL8WsLkX6yoLeQpU+dZAOuGcany5qkYQMmj35ZrKgb6yiyYqpUzFOpR8mZQkgqeEQ==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5284,6 +7680,89 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=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-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5298,7 +7777,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5311,6 +7789,16 @@ "dev": true, "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5330,6 +7818,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -5391,6 +7888,29 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5401,6 +7921,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5422,6 +7957,30 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -5447,9 +8006,103 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/recharts": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", + "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5494,6 +8147,87 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "2.0.0-next.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", @@ -5549,6 +8283,22 @@ "node": ">=0.10.0" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5628,6 +8378,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -5644,6 +8400,76 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5693,6 +8519,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -5755,7 +8587,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -5768,7 +8599,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5778,7 +8608,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5798,7 +8627,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5815,7 +8643,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5834,7 +8661,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5859,6 +8685,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -5866,6 +8702,15 @@ "dev": true, "license": "MIT" }, + "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/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -5993,6 +8838,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6016,6 +8875,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -6086,6 +8963,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -6147,6 +9030,35 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -6205,6 +9117,45 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -6340,6 +9291,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.26.0.tgz", + "integrity": "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -6347,6 +9307,102 @@ "dev": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -6423,11 +9479,112 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/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/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -6538,6 +9695,12 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -6562,12 +9725,20 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, "node_modules/zod-validation-error": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", @@ -6580,6 +9751,16 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 453476a..a26f9d4 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,19 @@ "lint": "eslint" }, "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "@supabase/supabase-js": "^2.107.0", + "cheerio": "^1.2.0", + "date-fns": "^4.1.0", + "framer-motion": "^12.40.0", + "lucide-react": "^1.14.0", "next": "16.2.6", + "openai": "^6.37.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "react-markdown": "^10.1.0", + "recharts": "^3.8.1", + "remark-gfm": "^4.0.1" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/src/app/api/ai/analyze-link/route.ts b/src/app/api/ai/analyze-link/route.ts new file mode 100644 index 0000000..2a7489d --- /dev/null +++ b/src/app/api/ai/analyze-link/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { analyzeProject } from '@/lib/ai/analyzer'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { link, description } = body; + + if (!link && !description) { + return NextResponse.json( + { success: false, error: 'Informe o link do projeto ou descreva seu produto/negócio para analisar.' }, + { status: 400 } + ); + } + + const analysis = await analyzeProject({ link, description }); + + return NextResponse.json({ success: true, data: analysis }); + } catch (error) { + console.error('[AI] Error analyzing project link:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'Erro ao analisar o link.', + }, + { status: 500 } + ); + } +} diff --git a/src/app/api/ai/generate-audience/route.ts b/src/app/api/ai/generate-audience/route.ts new file mode 100644 index 0000000..e0fe380 --- /dev/null +++ b/src/app/api/ai/generate-audience/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { generateAudienceSuggestion } from '@/lib/ai/copywriter'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { context, link, category } = body; + + if (!context && !link) { + return NextResponse.json( + { success: false, error: 'É necessário preencher os campos do formulário ou o Link.' }, + { status: 400 } + ); + } + + const suggestion = await generateAudienceSuggestion(context, link, category); + + return NextResponse.json({ success: true, data: suggestion }); + } catch (error) { + console.error('[AI] Error generating audience:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'AI error', + }, + { status: 500 } + ); + } +} diff --git a/src/app/api/ai/generate-copy/route.ts b/src/app/api/ai/generate-copy/route.ts new file mode 100644 index 0000000..93f3ae8 --- /dev/null +++ b/src/app/api/ai/generate-copy/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { generateAdCopy } from '@/lib/ai/copywriter'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { context, copy_rules, objective, tone, count, category } = body; + + if (!context || !objective) { + return NextResponse.json( + { success: false, error: 'context and objective are required' }, + { status: 400 } + ); + } + + const variations = await generateAdCopy({ context, copy_rules, objective, tone, count, category }); + + return NextResponse.json({ success: true, data: variations }); + } catch (error) { + console.error('[AI] Error generating copy:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'AI error', + details: 'Verifique se a OPENAI_API_KEY no .env.local é uma chave válida da OpenAI (começa com sk-...)' + }, + { status: 500 } + ); + } +} diff --git a/src/app/api/ai/generate-image/route.ts b/src/app/api/ai/generate-image/route.ts new file mode 100644 index 0000000..98ee044 --- /dev/null +++ b/src/app/api/ai/generate-image/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { generateAdImage } from '@/lib/ai/creative'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { context, style, dimensions, mood } = body; + + if (!context || !style) { + return NextResponse.json( + { success: false, error: 'context and style are required' }, + { status: 400 } + ); + } + + const dimensionsList: ('1080x1080' | '1080x1920' | '1200x628')[] = [ + '1080x1080', + '1080x1920', + '1200x628' + ]; + + const imagePromises = dimensionsList.map((dim) => + generateAdImage({ + context, + style, + dimensions: dim, + mood, + }) + ); + + const images = await Promise.all(imagePromises); + + return NextResponse.json({ success: true, data: images }); + } catch (error) { + console.error('[AI] Error generating image:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'AI error' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/analysis/chat/route.ts b/src/app/api/analysis/chat/route.ts new file mode 100644 index 0000000..627b4dc --- /dev/null +++ b/src/app/api/analysis/chat/route.ts @@ -0,0 +1,53 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getAIClient } from '@/lib/ai/client'; +import { getAccessToken } from '@/lib/auth/session'; + +const CHAT_MODEL = 'gpt-5.4-mini'; + +export async function POST(request: NextRequest) { + try { + const token = await getAccessToken(); + if (!token) { + return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); + } + + const { messages, campaignName, reportContext } = await request.json(); + + if (!messages || !Array.isArray(messages)) { + return NextResponse.json({ success: false, error: 'Messages required' }, { status: 400 }); + } + + const openai = getAIClient(); + + const systemPrompt = `Você é um Consultor Sênior de Tráfego Pago conversando com o usuário sobre a campanha: "${campaignName || 'Campanha Desconhecida'}". +Sua missão é responder dúvidas técnicas do usuário sobre essa campanha, dar ideias de copy, explicar métricas e sugerir otimizações de forma amigável, direta e profissional. Use formatação Markdown (negrito, listas, etc) para deixar a resposta legível e bem estruturada. + +[CONTEXTO/RELATÓRIO DA CAMPANHA] +${JSON.stringify(reportContext, null, 2)}`; + + const chatMessages = [ + { role: 'system' as const, content: systemPrompt }, + ...messages.map((msg: any) => ({ + role: msg.role === 'user' ? ('user' as const) : ('assistant' as const), + content: msg.content, + })), + ]; + + const completion = await openai.chat.completions.create({ + model: CHAT_MODEL, + messages: chatMessages, + }); + + return NextResponse.json({ + success: true, + data: { reply: completion.choices[0]?.message?.content || '' } + }); + + } catch (error) { + console.error('[Chat API] Error:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/analysis/route.ts b/src/app/api/analysis/route.ts new file mode 100644 index 0000000..706d05f --- /dev/null +++ b/src/app/api/analysis/route.ts @@ -0,0 +1,144 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { getCampaignDetails } from '@/lib/mcp/tools'; +import { getAccessToken } from '@/lib/auth/session'; +import { getAIClient } from '@/lib/ai/client'; + +const ANALYSIS_MODEL = 'gpt-5.4-mini'; + +export async function POST(request: NextRequest) { + try { + const token = await getAccessToken(); + if (!token) { + return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); + } + + const { campaignId } = await request.json(); + if (!campaignId) { + return NextResponse.json({ success: false, error: 'Campaign ID required' }, { status: 400 }); + } + + const client = await createMetaMCPClient(token); + let campaignData: any; + + try { + const result = await getCampaignDetails(client, campaignId); + campaignData = Array.isArray(result) ? result[0] : result; + } finally { + await client.close(); + } + + if (!campaignData || !campaignData.id) { + return NextResponse.json({ success: false, error: 'Campaign not found on Meta' }, { status: 404 }); + } + + // Prepare data for the AI model + const insights = campaignData.insights?.data?.[0] || {}; + const adsets = campaignData.adsets?.data || []; + + let totalSpend = parseFloat(insights.spend || '0'); + let roas = parseFloat(insights.purchase_roas?.[0]?.value || '0'); + + // Extract Ads & Creatives + const adsToAnalyze = []; + for (const adset of adsets) { + const ads = adset.ads?.data || []; + for (const ad of ads) { + const adInsights = ad.insights?.data?.[0] || {}; + const creative = ad.creative || {}; + + // Extract texts + let body = creative.body || ''; + let title = creative.title || ''; + let imageUrl = creative.image_url || creative.thumbnail_url || ''; + + // Handle asset_feed_spec (Dynamic Creative) + if (creative.asset_feed_spec) { + const feed = creative.asset_feed_spec; + body = feed.bodies?.map((b: any) => b.text).join(' | ') || body; + title = feed.titles?.map((t: any) => t.text).join(' | ') || title; + if (feed.images && feed.images.length > 0) { + imageUrl = feed.images[0].url || feed.images[0].thumbnail_url || imageUrl; // Just take the first image for now + } + } + + adsToAnalyze.push({ + ad_name: ad.name, + spend: adInsights.spend || '0', + roas: adInsights.purchase_roas?.[0]?.value || '0', + cpc: adInsights.cpc || '0', + ctr: adInsights.ctr || '0', + body, + title, + image_url: imageUrl, + }); + } + } + + const openai = getAIClient(); + + const prompt = ` +Você é um Especialista Sênior em Tráfego Pago e Copywriting. +Abaixo estão os dados reais extraídos de uma Campanha no Facebook Ads. +Por favor, faça uma auditoria completa. + +DADOS DA CAMPANHA: +Nome: ${campaignData.name} +Gasto Total (30d): R$ ${totalSpend} +ROAS Total: ${roas} + +ANÚNCIOS ATIVOS (CRIATIVOS E MÉTRICAS): +${JSON.stringify(adsToAnalyze, null, 2)} + +Sua tarefa é retornar um JSON ESTRITAMENTE VÁLIDO com a seguinte estrutura de análise (NÃO adicione formatação markdown \`\`\`json no início ou no fim). +Importante: Para as análises e ações, retorne um ARRAY de strings (cada string é um bullet point claro, curto e direto). +{ + "diagnostico_geral": "Um parágrafo forte resumindo a saúde da campanha.", + "analise_copy": ["Ponto 1 sobre a copy...", "Sugestão de melhoria de texto..."], + "analise_imagem": ["O que achei do visual...", "Ideia de novo formato (vídeo/carrossel)..."], + "analise_metricas": ["ROAS está X...", "CPA está Y..."], + "acao_recomendada": ["Ação imediata 1 (ex: Pausar Ad X)...", "Ação 2 (ex: Testar público Y)..."], + "nova_estrategia_sugerida": { + "nicho_sugerido": "ex: ecommerce, info_produto, negocios_locais, advogado...", + "produto_foco": "Nome ou tipo do produto que deve ser focado na próxima campanha", + "publico_alvo": "Qual deve ser o público alvo ideal", + "angulos_de_venda": ["Ângulo 1", "Ângulo 2"] + } +} + `; + + const completion = await openai.chat.completions.create({ + model: ANALYSIS_MODEL, + messages: [{ role: 'user', content: prompt }], + response_format: { type: 'json_object' }, + }); + const textRes = completion.choices[0]?.message?.content || ''; + let jsonRes; + + try { + // Clean up markdown fences if model ignored instruction + const cleanedText = textRes.replace(/^```json/m, '').replace(/```$/m, '').trim(); + jsonRes = JSON.parse(cleanedText); + } catch (e) { + console.error('Failed to parse AI response:', textRes); + return NextResponse.json({ success: false, error: 'Failed to parse AI response' }, { status: 500 }); + } + + return NextResponse.json({ + success: true, + data: { + campaign_metrics: { spend: totalSpend, roas }, + ads_count: adsToAnalyze.length, + ads: adsToAnalyze, + analysis: jsonRes + } + }); + + } catch (error) { + console.error('[Analysis] API Error:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/auth/callback/route.ts b/src/app/api/auth/callback/route.ts new file mode 100644 index 0000000..33620dc --- /dev/null +++ b/src/app/api/auth/callback/route.ts @@ -0,0 +1,81 @@ +// ============================================ +// OAuth — Callback (exchange code for token) +// ============================================ + +import { NextRequest, NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; +import { setSessionCookies } from '@/lib/auth/session'; +import { getSettings } from '@/lib/settings'; + +export async function GET(request: NextRequest) { + const code = request.nextUrl.searchParams.get('code'); + const error = request.nextUrl.searchParams.get('error'); + + if (error || !code) { + const errorDesc = request.nextUrl.searchParams.get('error_description') || 'Login cancelled'; + return NextResponse.redirect( + new URL(`/login?error=${encodeURIComponent(errorDesc)}`, request.url) + ); + } + + const settings = getSettings(); + const appId = settings.metaAppId || process.env.META_APP_ID; + const appSecret = settings.metaAppSecret || process.env.META_APP_SECRET; + const redirectUri = process.env.META_REDIRECT_URI || 'http://localhost:3000/api/auth/callback'; + + if (!appId || !appSecret) { + return NextResponse.redirect( + new URL(`/login?error=${encodeURIComponent('Credenciais Meta não configuradas no sistema.')}`, request.url) + ); + } + + try { + // 1. Exchange code for short-lived token + const tokenUrl = new URL('https://graph.facebook.com/v20.0/oauth/access_token'); + tokenUrl.searchParams.set('client_id', appId); + tokenUrl.searchParams.set('client_secret', appSecret); + tokenUrl.searchParams.set('redirect_uri', redirectUri); + tokenUrl.searchParams.set('code', code); + + const tokenRes = await fetch(tokenUrl.toString()); + const tokenData = await tokenRes.json(); + + if (tokenData.error) { + throw new Error(tokenData.error.message); + } + + const shortToken = tokenData.access_token; + + // 2. Exchange for long-lived token (60 days) + const longTokenUrl = new URL('https://graph.facebook.com/v20.0/oauth/access_token'); + longTokenUrl.searchParams.set('grant_type', 'fb_exchange_token'); + longTokenUrl.searchParams.set('client_id', appId); + longTokenUrl.searchParams.set('client_secret', appSecret); + longTokenUrl.searchParams.set('fb_exchange_token', shortToken); + + const longRes = await fetch(longTokenUrl.toString()); + const longData = await longRes.json(); + const accessToken = longData.access_token || shortToken; + + // 3. Get user info + const meRes = await fetch( + `https://graph.facebook.com/v20.0/me?fields=name,picture.type(large)&access_token=${accessToken}` + ); + const meData = await meRes.json(); + + // 4. Save session + const cookieStore = await cookies(); + setSessionCookies(cookieStore, accessToken, { + name: meData.name || 'User', + picture: meData.picture?.data?.url || '', + }); + + // 5. Redirect to dashboard + return NextResponse.redirect(new URL('/dashboard', request.url)); + } catch (err) { + console.error('[Auth] OAuth error:', err); + return NextResponse.redirect( + new URL(`/login?error=${encodeURIComponent('Erro na autenticação')}`, request.url) + ); + } +} diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000..e32f642 --- /dev/null +++ b/src/app/api/auth/logout/route.ts @@ -0,0 +1,13 @@ +// ============================================ +// Auth — Logout +// ============================================ + +import { NextRequest, NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; +import { clearSessionCookies } from '@/lib/auth/session'; + +export async function POST(request: NextRequest) { + const cookieStore = await cookies(); + clearSessionCookies(cookieStore); + return NextResponse.redirect(new URL('/login', request.url)); +} diff --git a/src/app/api/auth/meta/route.ts b/src/app/api/auth/meta/route.ts new file mode 100644 index 0000000..2664064 --- /dev/null +++ b/src/app/api/auth/meta/route.ts @@ -0,0 +1,37 @@ +// ============================================ +// OAuth — Redirect to Meta Login +// ============================================ + +import { NextResponse } from 'next/server'; +import { getSettings } from '@/lib/settings'; + +export async function GET() { + const settings = getSettings(); + const appId = settings.metaAppId || process.env.META_APP_ID; + const redirectUri = process.env.META_REDIRECT_URI || 'http://localhost:3000/api/auth/callback'; + + if (!appId || appId === 'your_meta_app_id') { + return NextResponse.json( + { error: 'App ID não configurado. Acesse a tela de login e clique em Configurar.' }, + { status: 500 } + ); + } + + const scopes = [ + 'ads_management', + 'ads_read', + 'business_management', + 'pages_show_list', + 'pages_read_engagement', + 'instagram_basic', + ].join(','); + + const authUrl = new URL('https://www.facebook.com/v20.0/dialog/oauth'); + authUrl.searchParams.set('client_id', appId); + authUrl.searchParams.set('redirect_uri', redirectUri); + authUrl.searchParams.set('scope', scopes); + authUrl.searchParams.set('response_type', 'code'); + authUrl.searchParams.set('state', crypto.randomUUID()); + + return NextResponse.redirect(authUrl.toString()); +} diff --git a/src/app/api/campaigns/[id]/budget/route.ts b/src/app/api/campaigns/[id]/budget/route.ts new file mode 100644 index 0000000..78a1ad0 --- /dev/null +++ b/src/app/api/campaigns/[id]/budget/route.ts @@ -0,0 +1,45 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { updateCampaignBudget } from '@/lib/mcp/tools'; +import { getAccessToken } from '@/lib/auth/session'; + +export async function PATCH( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const token = await getAccessToken(); + if (!token) { + return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); + } + + const { id } = await params; + const body = await request.json(); + const { daily_budget } = body; + + if (!daily_budget || typeof daily_budget !== 'number' || daily_budget <= 0) { + return NextResponse.json( + { success: false, error: 'Invalid budget. Must be a positive number in cents.' }, + { status: 400 } + ); + } + + const client = await createMetaMCPClient(token); + + try { + await updateCampaignBudget(client, id, daily_budget); + return NextResponse.json({ + success: true, + data: { campaignId: id, daily_budget }, + }); + } finally { + await client.close(); + } + } catch (error) { + console.error('[API] Error updating campaign budget:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Failed to update budget' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/campaigns/[id]/status/route.ts b/src/app/api/campaigns/[id]/status/route.ts new file mode 100644 index 0000000..b53142c --- /dev/null +++ b/src/app/api/campaigns/[id]/status/route.ts @@ -0,0 +1,50 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { pauseCampaign, resumeCampaign } from '@/lib/mcp/tools'; +import { getAccessToken } from '@/lib/auth/session'; + +export async function PATCH( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const token = await getAccessToken(); + if (!token) { + return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); + } + + const { id } = await params; + const body = await request.json(); + const { status } = body; + + if (!status || !['ACTIVE', 'PAUSED'].includes(status)) { + return NextResponse.json( + { success: false, error: 'Invalid status. Must be ACTIVE or PAUSED.' }, + { status: 400 } + ); + } + + const client = await createMetaMCPClient(token); + + try { + if (status === 'PAUSED') { + await pauseCampaign(client, id); + } else { + await resumeCampaign(client, id); + } + + return NextResponse.json({ + success: true, + data: { campaignId: id, status }, + }); + } finally { + await client.close(); + } + } catch (error) { + console.error('[API] Error updating campaign status:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Failed to update status' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/campaigns/create/route.ts b/src/app/api/campaigns/create/route.ts new file mode 100644 index 0000000..c81f9c9 --- /dev/null +++ b/src/app/api/campaigns/create/route.ts @@ -0,0 +1,270 @@ +import { NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { createFullCampaign, getPages } from '@/lib/mcp/tools'; + +function toNumberOrUndefined(value: unknown): number | undefined { + if (value === undefined || value === null || value === '') return undefined; + + const numberValue = Number(value); + + if (!Number.isFinite(numberValue)) { + return undefined; + } + + return numberValue; +} + +function normalizeMoneyInCents(value: unknown, fallback: number): number { + const numberValue = toNumberOrUndefined(value); + + if (numberValue === undefined) { + return fallback; + } + + if (numberValue > 0 && numberValue < 100) { + throw new Error( + `Valor de orçamento/bid inválido: ${numberValue}. Envie em centavos. Exemplo: R$20,00 = 2000.` + ); + } + + return Math.round(numberValue); +} + +function getFirstPageIdFromPages(pages: unknown): string | undefined { + if (!Array.isArray(pages)) return undefined; + + const firstPage = pages.find((page: any) => page?.id); + + return firstPage?.id; +} + +function isWhatsappCampaign(body: Record) { + const cta = String(body.cta || '').toUpperCase(); + const destinationType = String(body.destination_type || '').toUpperCase(); + + return ( + body.is_whatsapp === true || + destinationType === 'WHATSAPP' || + cta === 'WHATSAPP_MESSAGE' || + Boolean(body.whatsapp_link) || + Boolean(body.whatsapp_phone_number) + ); +} + +export async function POST(req: Request) { + let mcpClient: Awaited> | undefined; + + try { + const cookieStore = await cookies(); + const token = cookieStore.get('meta_access_token')?.value; + + if (!token) { + return NextResponse.json( + { + success: false, + error: 'Unauthorized: meta_access_token cookie não encontrado.', + }, + { status: 401 } + ); + } + + const body = await req.json(); + + const { + campaign_name, + objective, + daily_budget, + bid_strategy, + bid_amount, + targeting, + page_id, + instagram_actor_id, + image_urls, + image_bytes, + link, + headline, + primary_text, + description, + cta, + ad_account_id, + optimization_goal, + billing_event, + destination_type, + promoted_object, + whatsapp_link, + whatsapp_phone_number, + } = body; + + if (!campaign_name) { + return NextResponse.json( + { success: false, error: 'campaign_name é obrigatório.' }, + { status: 400 } + ); + } + + if (!link && !whatsapp_link && !whatsapp_phone_number) { + return NextResponse.json( + { + success: false, + error: 'link, whatsapp_link ou whatsapp_phone_number é obrigatório.', + }, + { status: 400 } + ); + } + + if (!headline) { + return NextResponse.json( + { success: false, error: 'headline é obrigatório.' }, + { status: 400 } + ); + } + + if (!primary_text) { + return NextResponse.json( + { success: false, error: 'primary_text é obrigatório.' }, + { status: 400 } + ); + } + + const hasUrls = Array.isArray(image_urls) && image_urls.length > 0; + const hasBytes = Array.isArray(image_bytes) && image_bytes.length > 0; + + if (!hasUrls && !hasBytes) { + return NextResponse.json( + { + success: false, + error: 'image_urls ou image_bytes (array) é obrigatório.', + }, + { status: 400 } + ); + } + + mcpClient = await createMetaMCPClient(token); + + let finalPageId = page_id; + + if (!finalPageId || finalPageId === '123456789') { + const pages = await getPages(mcpClient); + finalPageId = getFirstPageIdFromPages(pages); + } + + if (!finalPageId) { + return NextResponse.json( + { + success: false, + error: + 'Nenhuma página Meta encontrada. Envie page_id manualmente ou conecte uma página ao token.', + }, + { status: 400 } + ); + } + + const finalObjective = objective || 'OUTCOME_TRAFFIC'; + const finalIsWhatsapp = isWhatsappCampaign(body); + + const finalDailyBudget = normalizeMoneyInCents(daily_budget, 5000); + const finalBidAmount = toNumberOrUndefined(bid_amount); + + if (bid_strategy === 'LOWEST_COST_WITH_BID_CAP' && !finalBidAmount) { + return NextResponse.json( + { + success: false, + error: + 'bid_amount é obrigatório quando bid_strategy = LOWEST_COST_WITH_BID_CAP. Envie em centavos. Exemplo: R$10,00 = 1000.', + }, + { status: 400 } + ); + } + + if (finalBidAmount !== undefined && finalBidAmount > 0 && finalBidAmount < 100) { + return NextResponse.json( + { + success: false, + error: + 'bid_amount deve ser enviado em centavos. Exemplo: R$10,00 = 1000, não 10.', + }, + { status: 400 } + ); + } + + if (bid_strategy === 'BID_CAP') { + if (!finalBidAmount || finalBidAmount <= 0) { + return NextResponse.json( + { error: 'Você selecionou a estratégia de "Bid Cap", mas não informou o valor do lance (Bid). Por favor, volte e defina o valor do Bid Cap ou mude para "Volume Máximo".' }, + { status: 400 } + ); + } + } + const { pixel_id } = body; + + let finalOptimizationGoal = optimization_goal; + let finalPromotedObject = promoted_object; + + if (finalObjective === 'OUTCOME_SALES' && pixel_id) { + finalOptimizationGoal = 'OFFSITE_CONVERSIONS'; + finalPromotedObject = { + pixel_id: pixel_id, + custom_event_type: 'PURCHASE', + }; + } + + const result = await createFullCampaign(mcpClient, { + campaign_name, + objective: finalObjective, + daily_budget: finalDailyBudget, + bid_strategy: bid_strategy === 'BID_CAP' ? 'LOWEST_COST_WITH_BID_CAP' : 'LOWEST_COST_WITHOUT_CAP', + bid_amount: bid_amount, + targeting: { + ...(targeting || { + geo_locations: { + countries: ['BR'], + }, + }), + targeting_automation: { + advantage_audience: 0, + }, + }, + page_id: finalPageId, + instagram_actor_id: instagram_actor_id || undefined, + image_urls, + image_bytes, + link, + headline, + primary_text, + description: description || '', + cta: cta || (finalIsWhatsapp ? 'WHATSAPP_MESSAGE' : 'LEARN_MORE'), + ad_account_id, + optimization_goal: finalOptimizationGoal, + billing_event, + destination_type: destination_type || (finalIsWhatsapp ? 'WHATSAPP' : undefined), + promoted_object: finalPromotedObject, + is_whatsapp: finalIsWhatsapp, + whatsapp_link, + whatsapp_phone_number, + }); + + return NextResponse.json({ + success: true, + data: result, + }); + } catch (error: any) { + console.error('Create Campaign API Error:', error); + + return NextResponse.json( + { + success: false, + error: error?.message || 'Internal Server Error', + details: { + message: error?.message, + stack: process.env.NODE_ENV === 'development' ? error?.stack : undefined, + }, + }, + { status: 500 } + ); + } finally { + if (mcpClient) { + await mcpClient.close(); + } + } +} \ No newline at end of file diff --git a/src/app/api/campaigns/draft/[id]/route.ts b/src/app/api/campaigns/draft/[id]/route.ts new file mode 100644 index 0000000..1790d5a --- /dev/null +++ b/src/app/api/campaigns/draft/[id]/route.ts @@ -0,0 +1,24 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getSupabaseServerClient } from '@/lib/supabase/server'; + +export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params; + const supabase = getSupabaseServerClient(); + + const { data, error } = await supabase + .from('agent_campaigns') + .select('*') + .eq('id', id) + .single(); + + if (error) throw error; + return NextResponse.json({ success: true, data }); + } catch (error) { + console.error('[Campaigns] Error fetching draft:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Erro ao buscar campanha salva.' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/campaigns/draft/route.ts b/src/app/api/campaigns/draft/route.ts new file mode 100644 index 0000000..cc330f9 --- /dev/null +++ b/src/app/api/campaigns/draft/route.ts @@ -0,0 +1,93 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getSupabaseServerClient } from '@/lib/supabase/server'; + +const ALLOWED_FIELDS = [ + 'product', + 'audience', + 'objective', + 'budget_daily', + 'copy_variations', + 'image_urls', + 'targeting', + 'config', + 'status', + 'meta_campaign_id', + 'meta_adset_id', + 'meta_creative_id', + 'meta_ad_id', + 'approved_at', +] as const; + +function pickAllowedFields(body: Record) { + const data: Record = {}; + for (const field of ALLOWED_FIELDS) { + if (body[field] !== undefined) data[field] = body[field]; + } + return data; +} + +// Saves or updates a campaign draft so generated copy/images survive errors +// during publish (Meta API failures, etc.) without needing to regenerate. +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { id } = body; + + const supabase = getSupabaseServerClient(); + const data = pickAllowedFields(body); + + if (id) { + const { data: row, error } = await supabase + .from('agent_campaigns') + .update(data as never) + .eq('id', id) + .select() + .single(); + + if (error) throw error; + return NextResponse.json({ success: true, data: row }); + } + + if (!data.product) { + return NextResponse.json( + { success: false, error: 'product é obrigatório para salvar o rascunho.' }, + { status: 400 } + ); + } + + const { data: row, error } = await supabase + .from('agent_campaigns') + .insert({ ...data, status: data.status || 'draft' } as never) + .select() + .single(); + + if (error) throw error; + return NextResponse.json({ success: true, data: row }); + } catch (error) { + console.error('[Campaigns] Error saving draft:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Erro ao salvar rascunho da campanha.' }, + { status: 500 } + ); + } +} + +export async function GET(request: NextRequest) { + try { + const supabase = getSupabaseServerClient(); + const { data, error } = await supabase + .from('agent_campaigns') + .select('*') + .order('created_at', { ascending: false }) + .limit(50); + + if (error) throw error; + return NextResponse.json({ success: true, data }); + } catch (error) { + console.error('[Campaigns] Error listing drafts:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Erro ao listar campanhas salvas.' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/campaigns/insights/route.ts b/src/app/api/campaigns/insights/route.ts new file mode 100644 index 0000000..157879b --- /dev/null +++ b/src/app/api/campaigns/insights/route.ts @@ -0,0 +1,53 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { getAccountInsights } from '@/lib/mcp/tools'; +import { getAccessToken } from '@/lib/auth/session'; + +export async function GET(request: NextRequest) { + try { + const token = await getAccessToken(); + if (!token) { + return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); + } + + const searchParams = request.nextUrl.searchParams; + const datePreset = searchParams.get('date_preset') || 'last_30d'; + + const client = await createMetaMCPClient(token); + + try { + const rawOverview: any = await getAccountInsights(client, datePreset); + + // Calculate total leads from actions + const actions = rawOverview?.actions || []; + const totalLeads = actions.find((a: any) => a.action_type === 'lead')?.value || 0; + + const purchaseValue = actions.find((a: any) => a.action_type === 'omni_purchase')?.value || 0; + const spend = parseFloat(rawOverview?.spend || '0'); + const roas = spend > 0 && purchaseValue ? (parseFloat(purchaseValue) / spend) : 0; + + // Map to expected AccountOverview structure + const overview = { + total_spend: spend, + total_leads: parseInt(totalLeads, 10), + total_clicks: parseInt(rawOverview?.clicks || '0', 10), + total_impressions: parseInt(rawOverview?.impressions || '0', 10), + total_roas: roas, + avg_ctr: parseFloat(rawOverview?.ctr || '0'), + avg_cpc: parseFloat(rawOverview?.cpc || '0'), + avg_cpm: parseFloat(rawOverview?.cpm || '0'), + campaigns_active: 0, // Not available directly in insights, could fetch if needed + }; + + return NextResponse.json({ success: true, data: overview }); + } finally { + await client.close(); + } + } catch (error) { + console.error('[API] Error fetching insights:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Failed to fetch insights' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/campaigns/route.ts b/src/app/api/campaigns/route.ts new file mode 100644 index 0000000..839dba0 --- /dev/null +++ b/src/app/api/campaigns/route.ts @@ -0,0 +1,87 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { listCampaigns } from '@/lib/mcp/tools'; +import { getAccessToken } from '@/lib/auth/session'; + +function mapInsights(rawInsights: any) { + if (!rawInsights || !rawInsights.data || rawInsights.data.length === 0) return null; + const item = rawInsights.data[0]; + + const actions = item.actions || []; + const leads = actions.find((a: any) => a.action_type === 'lead' || a.action_type === 'onsite_conversion.lead_grouped')?.value || 0; + const purchase = actions.find((a: any) => a.action_type === 'omni_purchase')?.value || 0; + const spendNum = parseFloat(item.spend || '0'); + const roas = spendNum > 0 && purchase ? parseFloat(purchase) / spendNum : 0; + + return { + spend: item.spend || '0', + ctr: item.ctr || '0', + cpc: item.cpc || '0', + cpm: item.cpm || '0', + leads: parseInt(leads, 10), + roas, + }; +} + +export async function GET(request: NextRequest) { + try { + const token = await getAccessToken(); + if (!token) { + return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); + } + + const searchParams = request.nextUrl.searchParams; + const datePreset = searchParams.get('date_preset') || 'last_30d'; + + const client = await createMetaMCPClient(token); + + try { + // Obter campanhas, adsets e ads COM insights aninhados usando datePreset + const campaigns = await listCampaigns(client, undefined, datePreset); + + const campaignsArray = Array.isArray(campaigns) ? campaigns : []; + + const withInsights = campaignsArray.map((campaign: any) => { + // Map campaign insights + const campaignInsights = mapInsights(campaign.insights); + + // Clean up raw insights from campaign object to keep it tidy, or just remap + const cleanedCampaign = { ...campaign }; + delete cleanedCampaign.insights; + + // Process AdSets + if (cleanedCampaign.adsets && cleanedCampaign.adsets.data) { + cleanedCampaign.adsets.data = cleanedCampaign.adsets.data.map((adset: any) => { + const adsetInsights = mapInsights(adset.insights); + const cleanedAdset = { ...adset }; + if (adsetInsights) cleanedAdset.insights = adsetInsights; + + // Process Ads inside AdSet + if (cleanedAdset.ads && cleanedAdset.ads.data) { + cleanedAdset.ads.data = cleanedAdset.ads.data.map((ad: any) => { + const adInsights = mapInsights(ad.insights); + const cleanedAd = { ...ad }; + if (adInsights) cleanedAd.insights = adInsights; + return cleanedAd; + }); + } + + return cleanedAdset; + }); + } + + return { campaign: cleanedCampaign, insights: campaignInsights }; + }); + + return NextResponse.json({ success: true, data: withInsights }); + } finally { + await client.close(); + } + } catch (error) { + console.error('[API] Error fetching campaigns:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Failed to fetch campaigns' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/cron/monitor/route.ts b/src/app/api/cron/monitor/route.ts new file mode 100644 index 0000000..8343ce6 --- /dev/null +++ b/src/app/api/cron/monitor/route.ts @@ -0,0 +1,70 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { evaluateCampaigns } from '@/lib/engine/monitor'; +import { DEFAULT_RULES } from '@/lib/engine/rules'; +import type { AutomationRule } from '@/lib/engine/rules'; + +export async function POST(request: NextRequest) { + // Verify cron secret + const authHeader = request.headers.get('authorization'); + const cronSecret = process.env.CRON_SECRET; + + if (cronSecret && cronSecret !== 'your_random_cron_secret') { + if (authHeader !== `Bearer ${cronSecret}`) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + } + + // For cron jobs, we need a stored token + // In production, store a long-lived System User token + const body = await request.json().catch(() => ({})); + const token = body.access_token || process.env.META_SYSTEM_TOKEN; + + if (!token) { + return NextResponse.json( + { success: false, error: 'No access token provided. Pass access_token in body or set META_SYSTEM_TOKEN env.' }, + { status: 401 } + ); + } + + try { + const rules: AutomationRule[] = DEFAULT_RULES.map((r, i) => ({ + ...r, + id: `default-${i}`, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + })); + + const client = await createMetaMCPClient(token); + + try { + const evaluations = await evaluateCampaigns(client, rules); + const triggered = evaluations.filter((e) => e.triggered); + + console.log(`[Cron] Monitor run: ${triggered.length}/${evaluations.length} rules triggered`); + + return NextResponse.json({ + success: true, + data: { + ran_at: new Date().toISOString(), + total_evaluated: evaluations.length, + total_triggered: triggered.length, + actions: triggered.map((e) => ({ + campaign: e.campaign_name, + rule: e.rule.name, + action: e.action_taken, + details: e.details, + })), + }, + }); + } finally { + await client.close(); + } + } catch (error) { + console.error('[Cron] Monitor error:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Monitor error' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/debug/route.ts b/src/app/api/debug/route.ts new file mode 100644 index 0000000..25124c9 --- /dev/null +++ b/src/app/api/debug/route.ts @@ -0,0 +1,15 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { getAccessToken } from '@/lib/auth/session'; + +export async function GET(request: NextRequest) { + try { + const token = await getAccessToken(); + if (!token) return NextResponse.json({ error: 'No token' }, { status: 401 }); + + const client = await createMetaMCPClient(token); + return NextResponse.json({ success: true }); + } catch (error: any) { + return NextResponse.json({ error: error.message || String(error), stack: error.stack }, { status: 500 }); + } +} diff --git a/src/app/api/engine/rules/route.ts b/src/app/api/engine/rules/route.ts new file mode 100644 index 0000000..0d328f5 --- /dev/null +++ b/src/app/api/engine/rules/route.ts @@ -0,0 +1,67 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { DEFAULT_RULES } from '@/lib/engine/rules'; +import type { AutomationRule } from '@/lib/engine/rules'; + +// In-memory store (replace with Supabase when configured) +let rules: AutomationRule[] = DEFAULT_RULES.map((r, i) => ({ + ...r, + id: `rule-${i + 1}`, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), +})); + +export async function GET() { + return NextResponse.json({ success: true, data: rules }); +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const newRule: AutomationRule = { + ...body, + id: `rule-${Date.now()}`, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + rules.push(newRule); + return NextResponse.json({ success: true, data: newRule }, { status: 201 }); + } catch (error) { + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Invalid data' }, + { status: 400 } + ); + } +} + +export async function PATCH(request: NextRequest) { + try { + const body = await request.json(); + const { id, ...updates } = body; + const index = rules.findIndex((r) => r.id === id); + if (index === -1) { + return NextResponse.json({ success: false, error: 'Rule not found' }, { status: 404 }); + } + rules[index] = { ...rules[index], ...updates, updated_at: new Date().toISOString() }; + return NextResponse.json({ success: true, data: rules[index] }); + } catch (error) { + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Invalid data' }, + { status: 400 } + ); + } +} + +export async function DELETE(request: NextRequest) { + try { + const { searchParams } = request.nextUrl; + const id = searchParams.get('id'); + if (!id) return NextResponse.json({ success: false, error: 'ID required' }, { status: 400 }); + rules = rules.filter((r) => r.id !== id); + return NextResponse.json({ success: true }); + } catch (error) { + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Error' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/engine/run/route.ts b/src/app/api/engine/run/route.ts new file mode 100644 index 0000000..c0f79b8 --- /dev/null +++ b/src/app/api/engine/run/route.ts @@ -0,0 +1,44 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { evaluateCampaigns } from '@/lib/engine/monitor'; +import { getAccessToken } from '@/lib/auth/session'; +import type { AutomationRule, ActivityLog } from '@/lib/engine/rules'; +import { DEFAULT_RULES } from '@/lib/engine/rules'; + +export async function POST(request: NextRequest) { + try { + const token = await getAccessToken(); + if (!token) { + return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); + } + + const body = await request.json().catch(() => ({})); + const rules: AutomationRule[] = body.rules || + DEFAULT_RULES.map((r, i) => ({ ...r, id: `default-${i}` })); + const recentLogs: ActivityLog[] = body.recent_logs || []; + + const client = await createMetaMCPClient(token); + + try { + const evaluations = await evaluateCampaigns(client, rules, recentLogs); + const triggered = evaluations.filter((e) => e.triggered); + + return NextResponse.json({ + success: true, + data: { + total_evaluated: evaluations.length, + total_triggered: triggered.length, + evaluations, + }, + }); + } finally { + await client.close(); + } + } catch (error) { + console.error('[Engine] Error running evaluations:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Engine error' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/meta/pages/route.ts b/src/app/api/meta/pages/route.ts new file mode 100644 index 0000000..aa238be --- /dev/null +++ b/src/app/api/meta/pages/route.ts @@ -0,0 +1,43 @@ +import { NextResponse } from 'next/server'; +import { createMetaMCPClient } from '@/lib/mcp/client'; +import { getPages } from '@/lib/mcp/tools'; +import { getAccessToken } from '@/lib/auth/session'; + +export async function GET() { + try { + const token = await getAccessToken(); + + if (!token) { + return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); + } + + const client = await createMetaMCPClient(token); + + try { + const pages = await getPages(client); + + const data = (Array.isArray(pages) ? pages : []).map((page: any) => ({ + id: page.id, + name: page.name, + instagram_account: page.instagram_business_account + ? { + id: page.instagram_business_account.id, + username: page.instagram_business_account.username, + profile_picture_url: page.instagram_business_account.profile_picture_url, + } + : null, + })); + + return NextResponse.json({ success: true, data }); + } finally { + await client.close(); + } + } catch (error: any) { + console.error('[API] Error listing Meta pages:', error); + + return NextResponse.json( + { success: false, error: error?.message || 'Erro ao buscar páginas do Facebook.' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/settings/route.ts b/src/app/api/settings/route.ts new file mode 100644 index 0000000..ab64288 --- /dev/null +++ b/src/app/api/settings/route.ts @@ -0,0 +1,24 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getSettings, saveSettings } from '@/lib/settings'; + +export async function GET() { + const settings = getSettings(); + + // Mask secrets so we don't send them back to the client + return NextResponse.json({ + metaAppId: settings.metaAppId, + hasMetaSecret: !!settings.metaAppSecret, + hasOpenaiKey: !!settings.openaiApiKey, + }); +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + saveSettings(body); + return NextResponse.json({ success: true }); + } catch (error) { + console.error('[API] Error saving settings:', error); + return NextResponse.json({ success: false, error: 'Failed to save settings' }, { status: 500 }); + } +} diff --git a/src/app/dashboard/activity/page.tsx b/src/app/dashboard/activity/page.tsx new file mode 100644 index 0000000..5da735f --- /dev/null +++ b/src/app/dashboard/activity/page.tsx @@ -0,0 +1,151 @@ +'use client'; + +import React, { useState } from 'react'; +import { Activity, CheckCircle, Clock, XCircle, Zap, Filter } from 'lucide-react'; + +interface LogEntry { + id: string; + timestamp: string; + campaign_name: string; + rule_name: string; + action: string; + details: string; + status: 'executed' | 'pending' | 'failed' | 'skipped'; +} + +// Demo data (will be replaced with Supabase query) +const DEMO_LOGS: LogEntry[] = [ + { + id: '1', timestamp: new Date(Date.now() - 3600000).toISOString(), + campaign_name: 'Campanha Conversão Q1', rule_name: 'CPL Alto — Pausar', + action: 'pause', details: 'CPL = R$67.30 (threshold: R$50.00)', status: 'executed', + }, + { + id: '2', timestamp: new Date(Date.now() - 7200000).toISOString(), + campaign_name: 'Remarketing Visitantes', rule_name: 'ROAS Baixo — Pausar', + action: 'pause', details: 'ROAS = 0.32x (threshold: 0.50x)', status: 'executed', + }, + { + id: '3', timestamp: new Date(Date.now() - 14400000).toISOString(), + campaign_name: 'Tráfego Blog', rule_name: 'CTR Muito Baixo — Pausar', + action: 'pause', details: 'CTR = 0.21% (threshold: 0.50%)', status: 'executed', + }, + { + id: '4', timestamp: new Date(Date.now() - 28800000).toISOString(), + campaign_name: 'Leads Premium', rule_name: 'ROAS Excelente — Aumentar Budget', + action: 'increase_budget', details: 'Budget +20%: R$50.00 → R$60.00. ROAS = 4.2x', status: 'executed', + }, + { + id: '5', timestamp: new Date(Date.now() - 43200000).toISOString(), + campaign_name: 'Campanha Vídeo', rule_name: 'CPC Alto — Reduzir Budget', + action: 'reduce_budget', details: 'Em cooldown (48h)', status: 'skipped', + }, +]; + +const statusConfig = { + executed: { icon: , color: '#4ade80', bg: 'rgba(34,197,94,0.1)', label: 'Executado' }, + pending: { icon: , color: '#fbbf24', bg: 'rgba(245,158,11,0.1)', label: 'Pendente' }, + failed: { icon: , color: '#f87171', bg: 'rgba(239,68,68,0.1)', label: 'Falhou' }, + skipped: { icon: , color: '#8888a0', bg: 'var(--overlay-05)', label: 'Pulado' }, +}; + +function timeAgo(dateStr: string): string { + const diff = Date.now() - new Date(dateStr).getTime(); + const mins = Math.floor(diff / 60000); + if (mins < 60) return `${mins}min atrás`; + const hours = Math.floor(mins / 60); + if (hours < 24) return `${hours}h atrás`; + const days = Math.floor(hours / 24); + return `${days}d atrás`; +} + +export default function ActivityPage() { + const [filter, setFilter] = useState('all'); + const logs = filter === 'all' ? DEMO_LOGS : DEMO_LOGS.filter((l) => l.status === filter); + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

Atividade

+

Ações executadas pelo agente de automação

+
+
+
+ + {['all', 'executed', 'pending', 'skipped', 'failed'].map((f) => ( + + ))} +
+
+ + {/* Stats */} +
+ {[ + { label: 'Total', value: DEMO_LOGS.length, color: 'var(--accent-soft-fg)' }, + { label: 'Executados', value: DEMO_LOGS.filter((l) => l.status === 'executed').length, color: '#4ade80' }, + { label: 'Pendentes', value: DEMO_LOGS.filter((l) => l.status === 'pending').length, color: '#fbbf24' }, + { label: 'Pulados', value: DEMO_LOGS.filter((l) => l.status === 'skipped').length, color: '#8888a0' }, + ].map((s) => ( +
+

{s.value}

+

{s.label}

+
+ ))} +
+ + {/* Timeline */} +
+
+

Timeline

+
+
+ {logs.length === 0 ? ( +
+

Nenhuma atividade encontrada

+
+ ) : ( + logs.map((log) => { + const sc = statusConfig[log.status]; + return ( +
+ {/* Timeline dot */} +
+
+ +
+
+ {/* Content */} +
+
+

{log.campaign_name}

+ + {sc.icon} {sc.label} + +
+

+ Regra: {log.rule_name} +

+

{log.details}

+
+ {/* Time */} + + {timeAgo(log.timestamp)} + +
+ ); + }) + )} +
+
+
+ ); +} diff --git a/src/app/dashboard/analysis/page.tsx b/src/app/dashboard/analysis/page.tsx new file mode 100644 index 0000000..64aacc0 --- /dev/null +++ b/src/app/dashboard/analysis/page.tsx @@ -0,0 +1,462 @@ +'use client'; +import { useRouter } from 'next/navigation'; + +import React, { useState, useEffect } from 'react'; +import { Spinner } from '@/components/ui/Spinner'; +import { Zap, AlertTriangle, TrendingUp, TrendingDown, RefreshCcw, Bot, Activity, Send, User } from 'lucide-react'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; + +type Campaign = { + id: string; + name: string; + status: string; +}; + +export default function AnalysisPage() { + const router = useRouter(); + const [campaigns, setCampaigns] = useState([]); + const [loadingList, setLoadingList] = useState(true); + + const [selectedCampaign, setSelectedCampaign] = useState(''); + const [analyzing, setAnalyzing] = useState(false); + const [report, setReport] = useState(null); + const [error, setError] = useState(''); + + // Chat State + const [chatMessages, setChatMessages] = useState<{role: string, content: string}[]>([]); + const [chatInput, setChatInput] = useState(''); + const [isChatting, setIsChatting] = useState(false); + + useEffect(() => { + fetch('/api/campaigns') + .then((res) => res.json()) + .then((data) => { + if (data.success) { + setCampaigns(data.data || []); + } + }) + .catch(() => {}) + .finally(() => setLoadingList(false)); + }, []); + + const runAnalysis = async () => { + if (!selectedCampaign) return; + + setAnalyzing(true); + setError(''); + setReport(null); + setChatMessages([]); + + try { + const res = await fetch('/api/analysis', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ campaignId: selectedCampaign }), + }); + const data = await res.json(); + + if (data.success) { + setReport(data.data); + } else { + setError(data.error || 'Erro ao analisar campanha'); + } + } catch (err) { + setError('Erro de conexão com o servidor'); + } finally { + setAnalyzing(false); + } + }; + + const sendChatMessage = async (e?: React.FormEvent) => { + if (e) e.preventDefault(); + if (!chatInput.trim() || isChatting || !report) return; + + const newMsg = { role: 'user', content: chatInput }; + const updatedMessages = [...chatMessages, newMsg]; + + setChatMessages(updatedMessages); + setChatInput(''); + setIsChatting(true); + + try { + const campName = campaigns.find(c => c.id === selectedCampaign)?.name || 'Campanha'; + const res = await fetch('/api/analysis/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + campaignName: campName, + reportContext: report, + messages: updatedMessages + }), + }); + const data = await res.json(); + if (data.success) { + setChatMessages([...updatedMessages, { role: 'model', content: data.data.reply }]); + } + } catch (err) { + console.error(err); + } finally { + setIsChatting(false); + } + }; + + const handleCreateNewCampaign = () => { + if (!report?.analysis?.nova_estrategia_sugerida) return; + const strat = report.analysis.nova_estrategia_sugerida; + const q = new URLSearchParams({ + niche: strat.nicho_sugerido || '', + product: strat.produto_foco || '', + audience: strat.publico_alvo || '' + }); + router.push(`/dashboard/create?${q.toString()}`); + }; + + return ( +
+ {/* Header */} +
+
+ +
+
+

Auditoria com IA

+

A IA fará um Raio-X dos criativos e métricas da campanha

+
+
+ +
+ +
+
+ {loadingList ? ( +
+ Carregando campanhas... +
+ ) : ( + + )} + {!loadingList && ( +
+ + + +
+ )} +
+ +
+
+ + {error && ( +
+

{error}

+
+ )} + + {analyzing && ( +
+
+ +
+

A IA está lendo seus anúncios...

+

+ Baixando as imagens, lendo os textos persuasivos e cruzando com o ROAS e o CPA na Meta. Isso pode levar alguns segundos. +

+
+ )} + + {report && !analyzing && ( +
+ {/* Métricas Hero */} +
+
+

Anúncios Auditados

+

{report.ads_count}

+
+
+

Gasto (30d)

+

R$ {parseFloat(report.campaign_metrics.spend).toFixed(2)}

+
+
+

ROAS

+

{parseFloat(report.campaign_metrics.roas).toFixed(2)}x

+
+
+ + {/* Anúncios Ativos na Campanha */} + {report.ads && report.ads.length > 0 && ( +
+

+ Anúncios Auditados +

+
+ {report.ads.map((ad: any, i: number) => ( +
+ {ad.image_url ? ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {ad.ad_name} +
+ ) : ( +
+ Sem imagem disponível +
+ )} +
+

{ad.ad_name}

+
+ Gasto: R$ {parseFloat(ad.spend).toFixed(2)} + ROAS: {parseFloat(ad.roas).toFixed(2)}x +
+

{ad.body || 'Sem texto'}

+
+
+ ))} +
+
+ )} + + {/* Relatório IA */} +
+ +
+

+ Diagnóstico Geral +

+

+ {report.analysis.diagnostico_geral} +

+
+ +
+
+

+ Análise de Texto (Copy) +

+
    + {Array.isArray(report.analysis.analise_copy) ? ( + report.analysis.analise_copy.map((txt: string, i: number) => ( +
  • {txt}
  • + )) + ) : ( +
  • {report.analysis.analise_copy}
  • + )} +
+
+ +
+

+ Análise Visual (Imagem) +

+
    + {Array.isArray(report.analysis.analise_imagem) ? ( + report.analysis.analise_imagem.map((txt: string, i: number) => ( +
  • {txt}
  • + )) + ) : ( +
  • {report.analysis.analise_imagem}
  • + )} +
+
+
+ +
+
+

+ Avaliação de Métricas +

+
    + {Array.isArray(report.analysis.analise_metricas) ? ( + report.analysis.analise_metricas.map((txt: string, i: number) => ( +
  • {txt}
  • + )) + ) : ( +
  • {report.analysis.analise_metricas}
  • + )} +
+
+ +
+

+ Plano de Ação Recomendado +

+
    + {Array.isArray(report.analysis.acao_recomendada) ? ( + report.analysis.acao_recomendada.map((txt: string, i: number) => ( +
  • {txt}
  • + )) + ) : ( +
  • {report.analysis.acao_recomendada}
  • + )} +
+
+
+ + {/* Nova Estratégia Sugerida */} + {report.analysis.nova_estrategia_sugerida && ( +
+
+
+ +
+
+

Estratégia Recomendada para a Próxima Campanha

+

Baseado no diagnóstico, a IA sugere os seguintes parâmetros para uma nova campanha

+
+
+ +
+
+
+
+

Nicho / Setor

+

{report.analysis.nova_estrategia_sugerida.nicho_sugerido}

+
+
+

Produto Foco

+

{report.analysis.nova_estrategia_sugerida.produto_foco}

+
+
+
+

Público-Alvo Ideal

+

{report.analysis.nova_estrategia_sugerida.publico_alvo}

+
+
+ +
+
+

Ângulos de Venda (Copy)

+
    + {report.analysis.nova_estrategia_sugerida.angulos_de_venda?.map((ang: string, i: number) => ( +
  • {ang}
  • + ))} +
+
+ +
+
+
+ )} + + {/* Chat com Especialista IA */} +
+
+
+ +
+
+

Consultor IA

+

Fale com a IA sobre esta campanha e tire suas dúvidas

+
+
+ +
+
+ {chatMessages.length === 0 ? ( +
+ +

Estou pronto para responder qualquer dúvida sobre essa análise. O que você gostaria de saber?

+
+ ) : ( + chatMessages.map((msg, i) => ( +
+
+ {msg.role === 'user' ? : } +
+
+ {msg.role === 'user' ? ( + msg.content + ) : ( +
+

, + ul: ({node, ...props}) =>

    , + ol: ({node, ...props}) =>
      , + li: ({node, ...props}) =>
    1. , + strong: ({node, ...props}) => + }} + > + {msg.content} + +
+ )} +
+
+ )) + )} + {isChatting && ( +
+
+ +
+
+
+
+
+
+
+ )} +
+ +
+ setChatInput(e.target.value)} + placeholder="Pergunte algo sobre a campanha..." + className="flex-1 bg-transparent border-none outline-none text-white px-3 text-sm" + disabled={isChatting} + /> + +
+
+
+ +
+
+ )} +
+ ); +} diff --git a/src/app/dashboard/automation/page.tsx b/src/app/dashboard/automation/page.tsx new file mode 100644 index 0000000..7bcc2e9 --- /dev/null +++ b/src/app/dashboard/automation/page.tsx @@ -0,0 +1,261 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Spinner } from '@/components/ui/Spinner'; +import { Modal } from '@/components/ui/Modal'; +import { + Shield, Plus, Trash2, Zap, AlertTriangle, + TrendingDown, TrendingUp, Bell, Pause, Bot +} from 'lucide-react'; +import type { AutomationRule, RuleMetric, RuleOperator, RuleAction } from '@/lib/engine/rules'; +import { METRIC_LABELS, OPERATOR_LABELS, ACTION_LABELS } from '@/lib/engine/rules'; + +const actionIcons: Record = { + pause: , + reduce_budget: , + increase_budget: , + notify: , + replace_with_ai: , +}; + +const actionColors: Record = { + pause: '#ef4444', + reduce_budget: '#f59e0b', + increase_budget: '#22c55e', + notify: '#3b82f6', + replace_with_ai: '#a855f7', +}; + +export default function AutomationPage() { + const [rules, setRules] = useState([]); + const [loading, setLoading] = useState(true); + const [showCreate, setShowCreate] = useState(false); + const [running, setRunning] = useState(false); + const [lastResult, setLastResult] = useState(null); + + // Form state + const [form, setForm] = useState({ + name: '', + metric: 'cpl' as RuleMetric, + operator: '>' as RuleOperator, + threshold: 50, + min_spend: 30, + min_impressions: 1000, + action: 'pause' as RuleAction, + action_value: 30, + cooldown_hours: 24, + }); + + useEffect(() => { + fetchRules(); + }, []); + + const fetchRules = async () => { + setLoading(true); + try { + const res = await fetch('/api/engine/rules'); + const data = await res.json(); + if (data.success) setRules(data.data); + } catch { /* ignore */ } + setLoading(false); + }; + + const toggleRule = async (rule: AutomationRule) => { + await fetch('/api/engine/rules', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: rule.id, enabled: !rule.enabled }), + }); + setRules((prev) => + prev.map((r) => (r.id === rule.id ? { ...r, enabled: !r.enabled } : r)) + ); + }; + + const deleteRule = async (id: string) => { + await fetch(`/api/engine/rules?id=${id}`, { method: 'DELETE' }); + setRules((prev) => prev.filter((r) => r.id !== id)); + }; + + const createRule = async () => { + const res = await fetch('/api/engine/rules', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ...form, enabled: true }), + }); + const data = await res.json(); + if (data.success) { + setRules((prev) => [...prev, data.data]); + setShowCreate(false); + setForm({ name: '', metric: 'cpl', operator: '>', threshold: 50, min_spend: 30, min_impressions: 1000, action: 'pause', action_value: 30, cooldown_hours: 24 }); + } + }; + + const runEngine = async () => { + setRunning(true); + setLastResult(null); + try { + const res = await fetch('/api/engine/run', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ rules }), + }); + const data = await res.json(); + if (data.success) { + setLastResult(`✅ Avaliação concluída: ${data.data.total_triggered} de ${data.data.total_evaluated} regras disparadas`); + } else { + setLastResult(`❌ Erro: ${data.error}`); + } + } catch { + setLastResult('❌ Erro ao executar engine'); + } + setRunning(false); + }; + + if (loading) return ( +
+ +
+ ); + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

Automação

+

Regras automáticas de monitoramento

+
+
+
+ + +
+
+ + {/* Last result banner */} + {lastResult && ( +
+

{lastResult}

+
+ )} + + {/* Rules list */} +
+ {rules.length === 0 ? ( +
+ +

Nenhuma regra configurada

+
+ ) : ( + rules.map((rule) => ( +
+
+
+ {/* Toggle */} + + {/* Info */} +
+

{rule.name}

+

+ Se {METRIC_LABELS[rule.metric]}{' '} + {OPERATOR_LABELS[rule.operator]}{' '} + + {rule.metric === 'ctr' ? `${rule.threshold}%` : rule.metric === 'roas' ? `${rule.threshold}x` : `R$ ${rule.threshold}`} + + {' → '} + + {ACTION_LABELS[rule.action]} + {rule.action_value ? ` ${rule.action_value}%` : ''} + +

+

+ Min: R${rule.min_spend} gasto, {rule.min_impressions} impressões • Cooldown: {rule.cooldown_hours}h +

+
+
+
+ + {actionIcons[rule.action]} {ACTION_LABELS[rule.action]} + + +
+
+
+ )) + )} +
+ + {/* Create Modal */} + setShowCreate(false)} title="Nova Regra de Automação"> +
+
+ + setForm({ ...form, name: e.target.value })} placeholder="Ex: CPL Alto — Pausar" /> +
+
+
+ + +
+
+ + +
+
+ + setForm({ ...form, threshold: parseFloat(e.target.value) })} /> +
+
+
+ + +
+
+
+ + setForm({ ...form, min_spend: parseFloat(e.target.value) })} /> +
+
+ + setForm({ ...form, min_impressions: parseInt(e.target.value) })} /> +
+
+ + setForm({ ...form, cooldown_hours: parseInt(e.target.value) })} /> +
+
+
+ + +
+
+
+
+ ); +} diff --git a/src/app/dashboard/create/page.tsx b/src/app/dashboard/create/page.tsx new file mode 100644 index 0000000..d5cbc64 --- /dev/null +++ b/src/app/dashboard/create/page.tsx @@ -0,0 +1,1438 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Spinner } from '@/components/ui/Spinner'; +import { ErrorBlock } from '@/components/ui/ErrorBlock'; +import { parseApiError, type FriendlyError } from '@/lib/errors'; +import { + Wand2, + Image as ImageIcon, + Type, + Send, + CheckCircle, + Upload, +} from 'lucide-react'; + +import { + TARGETING_TEMPLATES, +} from '@/lib/meta/targeting'; +import { SYSTEM_SKINS, getSkin } from '@/config/skins'; + +import type { AdCopyVariation } from '@/lib/ai/copywriter'; + +type Step = + | 'config' + | 'generating' + | 'preview' + | 'publishing' + | 'done'; + +type ImageSource = 'ai' | 'upload'; + +type MetaPage = { + id: string; + name: string; + instagram_account: { id: string; username: string; profile_picture_url?: string } | null; +}; + +export default function CreateCampaignPage() { + const [step, setStep] = useState('config'); + const [skinId, setSkinId] = useState('generic'); + const currentSkin = getSkin(skinId); + + const [customFields, setCustomFields] = useState>({}); + + const [form, setForm] = useState({ + campaign_name: '', + objective: currentSkin.meta_defaults.objective, + targeting_template: currentSkin.meta_defaults.targeting_template, + daily_budget: 50, + bid_strategy: currentSkin.meta_defaults.bid_strategy, + bid_amount: 10, + link: '', + pixel_id: '', + image_style: currentSkin.ai_guidelines.image_style, + }); + + const handleSkinChange = (newSkinId: string) => { + const skin = getSkin(newSkinId); + setSkinId(newSkinId); + setCustomFields({}); + setForm(prev => ({ + ...prev, + objective: skin.meta_defaults.objective, + targeting_template: skin.meta_defaults.targeting_template, + bid_strategy: skin.meta_defaults.bid_strategy, + image_style: skin.ai_guidelines.image_style, + })); + }; + + const [imageSource, setImageSource] = + useState('ai'); + + const [uploadedImageBytes, setUploadedImageBytes] = + useState(null); + + const [uploadedImagePreview, setUploadedImagePreview] = + useState(null); + + const [copyVariations, setCopyVariations] = + useState([]); + + const [selectedCopy, setSelectedCopy] = useState(0); + + const [generatedImages, setGeneratedImages] = + useState([]); + + const [audienceLoading, setAudienceLoading] = useState(false); + + const [analyzing, setAnalyzing] = useState(false); + const [analysisNote, setAnalysisNote] = useState(null); + const [productDescription, setProductDescription] = useState(''); + const [draftId, setDraftId] = useState(null); + + const [pages, setPages] = useState([]); + const [pagesLoading, setPagesLoading] = useState(false); + const [selectedPageId, setSelectedPageId] = useState(''); + + const selectedPage = pages.find((p) => p.id === selectedPageId) || null; + const selectedInstagramAccount = selectedPage?.instagram_account || null; + + // Loads the user's Facebook Pages (and their linked Instagram business accounts) + // so they can pick exactly which one promotes this campaign, instead of the + // backend silently falling back to the first page on the account. + useEffect(() => { + let cancelled = false; + + (async () => { + setPagesLoading(true); + try { + const res = await fetch('/api/meta/pages'); + const data = await res.json(); + if (!cancelled && data.success) { + const list: MetaPage[] = data.data || []; + setPages(list); + if (list.length > 0) { + setSelectedPageId((prev) => prev || list[0].id); + } + } + } catch { + // Não bloqueia o fluxo — o backend tem fallback para a primeira página da conta. + } finally { + if (!cancelled) setPagesLoading(false); + } + })(); + + return () => { + cancelled = true; + }; + }, []); + + // Saves/updates the campaign draft in Supabase so generated copy and images + // survive errors during publish (e.g. Meta API failures) without needing + // to be regenerated from scratch. + const saveDraft = async (fields: Record) => { + try { + const res = await fetch('/api/campaigns/draft', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: draftId || undefined, ...fields }), + }); + const data = await res.json(); + if (data.success && data.data?.id) { + setDraftId(data.data.id); + } + return data; + } catch { + return null; + } + }; + + const [error, setError] = useState(''); + const [errorDetail, setErrorDetail] = useState(null); + + const clearError = () => { + setError(''); + setErrorDetail(null); + }; + + const setApiError = (data: any, fallback?: string) => { + setErrorDetail(parseApiError(data, fallback)); + setError(''); + }; + + const showError = (message: string) => { + setError(message); + setErrorDetail(null); + }; + + useEffect(() => { + if (typeof window !== 'undefined') { + const searchParams = new URLSearchParams(window.location.search); + const niche = searchParams.get('niche'); + const product = searchParams.get('product'); + const audience = searchParams.get('audience'); + + if (niche || product || audience) { + // Try to match niche with a system skin, default to generic + let matchedSkinId = 'generic'; + if (niche) { + const lowerNiche = niche.toLowerCase(); + const possibleSkins = Object.keys(SYSTEM_SKINS); + matchedSkinId = possibleSkins.find(s => lowerNiche.includes(s) || s.includes(lowerNiche)) || 'generic'; + } + + setSkinId(matchedSkinId); + + // Auto-fill custom fields based on what the skin expects + const prefilledFields: Record = {}; + if (product) { + prefilledFields['product_name'] = product; + prefilledFields['service_name'] = product; + prefilledFields['imovel_type'] = product; + } + if (audience) { + prefilledFields['avatar'] = audience; + prefilledFields['target_audience'] = audience; + prefilledFields['pain_points'] = audience; + } + + setCustomFields(prefilledFields); + } + } + }, []); + + const handleFileUpload = ( + e: React.ChangeEvent + ) => { + const file = e.target.files?.[0]; + + if (!file) return; + + const reader = new FileReader(); + + reader.onload = (event) => { + const dataUrl = event.target?.result as string; + + setUploadedImagePreview(dataUrl); + setUploadedImageBytes(dataUrl); + }; + + reader.readAsDataURL(file); + }; + + const getContextString = () => { + return Object.entries(customFields) + .map(([key, value]) => { + const field = currentSkin.fields.find((f) => f.id === key); + return `${field?.label || key}: ${value}`; + }) + .join('\n'); + }; + + const generateAudience = async () => { + if (Object.keys(customFields).length === 0 && !form.link) { + showError('Preencha os campos ou o Link primeiro para gerar o público.'); + window.scrollTo({ top: 0, behavior: 'smooth' }); + return; + } + + setAudienceLoading(true); + clearError(); + + try { + const res = await fetch('/api/ai/generate-audience', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + context: getContextString(), + link: form.link, + category: skinId, + }), + }); + + const data = await res.json(); + if (!data.success) throw new Error(data.error); + + setCustomFields((prev) => ({ ...prev, audience_generated: data.data })); + } catch (err: any) { + showError(err.message || 'Erro ao gerar público-alvo.'); + } finally { + setAudienceLoading(false); + } + }; + + const analyzeWithAI = async () => { + if (!form.link && !productDescription.trim()) { + showError('Cole o link do projeto/site ou descreva seu produto/negócio antes de analisar com IA.'); + window.scrollTo({ top: 0, behavior: 'smooth' }); + return; + } + + setAnalyzing(true); + clearError(); + setAnalysisNote(null); + + try { + const res = await fetch('/api/ai/analyze-link', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ link: form.link || undefined, description: productDescription.trim() || undefined }), + }); + + const data = await res.json(); + if (!data.success) { + setApiError(data, 'Não foi possível analisar o link/descrição informados.'); + return; + } + + const analysis = data.data; + const skin = getSkin(analysis.skin_id); + + setSkinId(analysis.skin_id); + setCustomFields({ + ...analysis.fields, + audience_generated: analysis.audience || '', + }); + setForm((prev) => ({ + ...prev, + campaign_name: analysis.campaign_name || prev.campaign_name, + objective: analysis.objective || skin.meta_defaults.objective, + targeting_template: skin.meta_defaults.targeting_template, + bid_strategy: skin.meta_defaults.bid_strategy, + image_style: analysis.image_style || skin.ai_guidelines.image_style, + })); + setAnalysisNote(analysis.summary || null); + } catch (err: any) { + showError(err.message || 'Erro ao analisar o projeto com IA. Tente novamente em instantes.'); + } finally { + setAnalyzing(false); + } + }; + + const generateCreatives = async () => { + if ( + Object.keys(customFields).length === 0 || + !form.link || + !form.campaign_name + ) { + showError( + 'Preencha a Campanha, os campos da Skin e o Link antes de gerar os criativos.' + ); + window.scrollTo({ top: 0, behavior: 'smooth' }); + return; + } + + if ( + imageSource === 'upload' && + !uploadedImageBytes + ) { + showError( + 'Faça o upload de uma imagem antes de continuar.' + ); + window.scrollTo({ top: 0, behavior: 'smooth' }); + return; + } + + clearError(); + setStep('generating'); + + try { + // COPY + const copyRes = await fetch( + '/api/ai/generate-copy', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + context: getContextString(), + copy_rules: currentSkin.ai_guidelines.copy_rules, + objective: form.objective, + category: skinId, + count: 3, + }), + } + ); + + const copyData = await copyRes.json(); + + if (!copyData.success) { + throw new Error( + copyData.error || + 'Erro ao gerar textos.' + ); + } + + setCopyVariations(copyData.data); + setSelectedCopy(0); + + // IMAGE + let imagesForDraft: string[] = []; + + if (imageSource === 'ai') { + const imgRes = await fetch( + '/api/ai/generate-image', + { + method: 'POST', + headers: { + 'Content-Type': + 'application/json', + }, + body: JSON.stringify({ + context: getContextString(), + style: form.image_style, + }), + } + ); + + const imgData = await imgRes.json(); + + if (!imgData.success) { + throw new Error( + imgData.error || + 'Falha ao gerar imagem com IA.' + ); + } + + const urls = imgData.data.map((d: any) => d.url); + setGeneratedImages(urls); + imagesForDraft = urls; + } else { + if (uploadedImagePreview) { + setGeneratedImages([uploadedImagePreview]); + imagesForDraft = [uploadedImagePreview]; + } + } + + // Persist the draft now — if Meta publish fails later, the generated + // copy and images are safe in Supabase and won't need to be redone. + await saveDraft({ + product: form.campaign_name || customFields[currentSkin.fields[0]?.id] || 'Campanha sem nome', + audience: customFields['audience_generated'] || customFields['audience'] || '', + objective: form.objective, + budget_daily: form.daily_budget, + copy_variations: copyData.data, + image_urls: imagesForDraft, + targeting: { template: form.targeting_template }, + status: 'draft', + // Extra fields needed to retry publishing later without regenerating anything + config: { + campaign_name: form.campaign_name, + link: form.link, + pixel_id: form.pixel_id, + bid_strategy: form.bid_strategy, + bid_amount: form.bid_amount, + targeting_template: form.targeting_template, + skin_id: skinId, + }, + }); + + setStep('preview'); + } catch (err) { + showError( + err instanceof Error + ? err.message + : 'Erro ao gerar criativos. Tente novamente em instantes.' + ); + + setStep('config'); + } + }; + + const publishCampaign = async () => { + if (!copyVariations[selectedCopy]) { + showError( + 'Selecione uma copy antes de publicar.' + ); + + return; + } + + setStep('publishing'); + clearError(); + + try { + const selectedTemplate = + TARGETING_TEMPLATES.find( + (template) => + template.id === + form.targeting_template + ); + + const selectedTargeting = + selectedTemplate?.targeting; + + const res = await fetch( + '/api/campaigns/create', + { + method: 'POST', + headers: { + 'Content-Type': + 'application/json', + }, + body: JSON.stringify({ + campaign_name: + form.campaign_name, + + objective: + form.objective, + + daily_budget: Math.round( + form.daily_budget * 100 + ), + + bid_strategy: + form.bid_strategy === + 'BID_CAP' + ? 'LOWEST_COST_WITH_BID_CAP' + : undefined, + + bid_amount: + form.bid_strategy === + 'BID_CAP' + ? Math.round( + form.bid_amount * 100 + ) + : undefined, + + targeting: + selectedTargeting || { + geo_locations: { + countries: ['BR'], + }, + }, + + link: form.link, + + page_id: selectedPageId || undefined, + + instagram_actor_id: selectedInstagramAccount?.id || undefined, + + pixel_id: form.pixel_id, + + image_urls: + imageSource === 'ai' + ? generatedImages + : undefined, + + image_bytes: + imageSource === 'upload' + ? uploadedImageBytes + ? [uploadedImageBytes] + : [] + : undefined, + + headline: + copyVariations[selectedCopy] + .headline, + + primary_text: + copyVariations[selectedCopy] + .primary_text, + + description: + copyVariations[selectedCopy] + .description, + + cta: + copyVariations[selectedCopy] + .cta || 'LEARN_MORE', + }), + } + ); + + const data = await res.json(); + + if (!data.success) { + console.error('[Debug] Publish error response:', data); + setApiError(data, 'Não foi possível publicar a campanha na Meta.'); + + // Keep the draft as 'failed' (not lost) — copy/images stay saved so the + // user can retry the publish without regenerating everything. + await saveDraft({ status: 'failed' }); + setStep('preview'); + return; + } + + await saveDraft({ + status: 'published', + meta_campaign_id: data.data?.campaign_id, + meta_adset_id: data.data?.adset_id, + meta_creative_id: data.data?.creative_ids?.[0], + meta_ad_id: data.data?.ad_ids?.[0], + approved_at: new Date().toISOString(), + }); + + setStep('done'); + } catch (err) { + console.error('[Debug] Publish error:', err); + + setError( + err instanceof Error + ? err.message + : 'Erro fatal ao publicar campanha. Tente novamente em instantes.' + ); + setErrorDetail(null); + + // Keep the draft as 'failed' (not lost) — copy/images stay saved so the + // user can retry the publish without regenerating everything. + await saveDraft({ status: 'failed' }); + + setStep('preview'); + } + }; + + const objectives = [ + { + value: 'OUTCOME_TRAFFIC', + label: 'Tráfego', + }, + { + value: 'OUTCOME_LEADS', + label: 'Geração de Leads', + }, + { + value: 'OUTCOME_AWARENESS', + label: 'Reconhecimento', + }, + { + value: 'OUTCOME_ENGAGEMENT', + label: 'Engajamento', + }, + { + value: 'OUTCOME_SALES', + label: 'Vendas', + }, + ]; + + return ( +
+ {/* HEADER */} +
+
+ +
+ +
+

+ Criar Campanha Inteligente +

+ +

+ Configure orçamento, + Bid Cap e criativos + para campanhas Meta +

+
+
+ + {/* STEPS */} +
+ {[ + 'Configurar', + 'Gerando...', + 'Preview', + 'Publicando', + ].map((s, i) => { + const stepMap: Step[] = [ + 'config', + 'generating', + 'preview', + 'publishing', + ]; + + const isActive = + stepMap.indexOf(step) >= + i || step === 'done'; + + return ( + +
+
+ {step === 'done' && + i <= 3 ? ( + + ) : ( + i + 1 + )} +
+ + + {s} + +
+ + {i < 3 && ( +
+ )} + + ); + })} +
+ + {/* ERROR */} + {(errorDetail || error) && ( + + )} + + {/* CONFIG */} + {step === 'config' && ( +
+

+ Configuração da + Campanha +

+ +
+ {/* AI PROJECT INTAKE — single entry point: link and/or description, AI picks the skin and fills everything */} +
+
+ +

Comece por aqui: deixe a IA montar a campanha

+
+

+ Cole o link do seu site/produto e/ou descreva seu negócio em poucas palavras. A IA identifica o nicho ideal, escolhe o skin, e preenche nome da campanha, campos do nicho, público, objetivo e estilo visual — tudo de uma vez. +

+ +
+
+ + setForm({ ...form, link: e.target.value })} + placeholder="https://seusite.com" + /> +

+ Para onde o anúncio vai levar quem clicar — também é a fonte que a IA analisa. +

+
+ +
+ +