From 9ed616f2154deafb9b36e3e22be97019f93e293d Mon Sep 17 00:00:00 2001 From: deeqdev Date: Sat, 7 Feb 2026 17:36:26 +0300 Subject: [PATCH 1/5] Solidify sync engine + add web UI --- .env.local.example | 9 +- .gitignore | 6 + README.md | 255 +- eslint.config.js | 2 +- package.json | 6 +- src/cli.ts | 2 + src/config.ts | 1 + src/extended-fields.ts | 394 + src/http.ts | 3 +- src/model.ts | 25 +- src/providers/google.ts | 32 +- src/providers/microsoft.ts | 122 +- src/sync/engine.ts | 107 +- test/microsoftProvider.test.ts | 2 +- test/web.test.ts | 186 + tsup.config.ts | 27 +- web/.gitignore | 16 + web/app/api/auth/google/callback/route.ts | 59 + web/app/api/auth/google/route.ts | 26 + web/app/api/auth/microsoft/callback/route.ts | 71 + web/app/api/auth/microsoft/route.ts | 33 + web/app/api/disconnect/[provider]/route.ts | 16 + web/app/api/status/route.ts | 40 + web/app/api/sync/route.ts | 99 + web/app/globals.css | 124 + web/app/layout.tsx | 26 + web/app/page.tsx | 5 + web/components.json | 23 + web/components/dashboard.tsx | 430 + web/components/ui/alert.tsx | 66 + web/components/ui/badge.tsx | 48 + web/components/ui/button.tsx | 64 + web/components/ui/card.tsx | 92 + web/components/ui/separator.tsx | 28 + web/components/ui/sonner.tsx | 40 + web/lib/env.ts | 86 + web/lib/tokens.ts | 50 + web/lib/utils.ts | 6 + web/next.config.ts | 15 + web/package-lock.json | 8008 ++++++++++++++++++ web/package.json | 33 + web/postcss.config.mjs | 8 + web/tsconfig.json | 29 + 43 files changed, 10564 insertions(+), 156 deletions(-) create mode 100644 src/extended-fields.ts create mode 100644 test/web.test.ts create mode 100644 web/.gitignore create mode 100644 web/app/api/auth/google/callback/route.ts create mode 100644 web/app/api/auth/google/route.ts create mode 100644 web/app/api/auth/microsoft/callback/route.ts create mode 100644 web/app/api/auth/microsoft/route.ts create mode 100644 web/app/api/disconnect/[provider]/route.ts create mode 100644 web/app/api/status/route.ts create mode 100644 web/app/api/sync/route.ts create mode 100644 web/app/globals.css create mode 100644 web/app/layout.tsx create mode 100644 web/app/page.tsx create mode 100644 web/components.json create mode 100644 web/components/dashboard.tsx create mode 100644 web/components/ui/alert.tsx create mode 100644 web/components/ui/badge.tsx create mode 100644 web/components/ui/button.tsx create mode 100644 web/components/ui/card.tsx create mode 100644 web/components/ui/separator.tsx create mode 100644 web/components/ui/sonner.tsx create mode 100644 web/lib/env.ts create mode 100644 web/lib/tokens.ts create mode 100644 web/lib/utils.ts create mode 100644 web/next.config.ts create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/postcss.config.mjs create mode 100644 web/tsconfig.json diff --git a/.env.local.example b/.env.local.example index 4a00273..6fdc812 100644 --- a/.env.local.example +++ b/.env.local.example @@ -6,14 +6,15 @@ TASK_SYNC_PROVIDER_B=microsoft TASK_SYNC_STATE_DIR=.task-sync TASK_SYNC_LOG_LEVEL=info -# Google Tasks +# Google Tasks — https://console.cloud.google.com/ TASK_SYNC_GOOGLE_CLIENT_ID= TASK_SYNC_GOOGLE_CLIENT_SECRET= TASK_SYNC_GOOGLE_REFRESH_TOKEN= -TASK_SYNC_GOOGLE_TASKLIST_ID=@default +# TASK_SYNC_GOOGLE_TASKLIST_ID=@default -# Microsoft To Do +# Microsoft To Do — https://portal.azure.com/ TASK_SYNC_MS_CLIENT_ID= -TASK_SYNC_MS_TENANT_ID=common +TASK_SYNC_MS_CLIENT_SECRET= +TASK_SYNC_MS_TENANT_ID=consumers TASK_SYNC_MS_REFRESH_TOKEN= # TASK_SYNC_MS_LIST_ID= diff --git a/.gitignore b/.gitignore index 9b615bb..bb854b3 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,9 @@ yarn-error.log* # OS .DS_Store + +# web +web/.next/ +web/node_modules/ +web/out/ +web/next-env.d.ts diff --git a/README.md b/README.md index 2cd9ac6..0112ab3 100644 --- a/README.md +++ b/README.md @@ -2,162 +2,251 @@ Sync tasks between **Google Tasks** and **Microsoft To Do**. -Providers: +Works as a CLI for power users, or as a self-hosted web UI for everyone else. -- Google Tasks (OAuth refresh-token) -- Microsoft To Do via Microsoft Graph (OAuth refresh-token) +## Features -## Quickstart +- **Bidirectional sync** between Google Tasks and Microsoft To Do +- **Field-level conflict resolution** (last-write-wins per field) +- **Cold-start matching** — deduplicates tasks on first sync by title + notes +- **Delete propagation** — tombstones prevent resurrecting deleted tasks +- **Dry-run mode** — preview changes before applying +- **Polling mode** — auto-sync on an interval +- **Web UI** — connect accounts with OAuth, sync with one click +- **Self-hosted** — your data stays on your machine -### Requirements +## Requirements - Node.js **>= 22** -### Install +## Quick Start — Web UI -```bash -npm install -``` +The web UI lets you connect your Google and Microsoft accounts via OAuth +and sync tasks with one click. No manual token management needed. -### Build + run doctor +### 1. Set up OAuth apps -```bash -npm run build -node dist/cli.js doctor -``` +**Google Tasks:** + +1. Go to [Google Cloud Console](https://console.cloud.google.com/) +2. Create a project (or select existing) +3. Enable the **Google Tasks API** +4. Go to **APIs & Services → Credentials → Create Credentials → OAuth client ID** +5. Application type: **Web application** +6. Add authorized redirect URI: `http://localhost:3000/api/auth/google/callback` +7. Copy the **Client ID** and **Client Secret** -### Run sync once +**Microsoft To Do:** + +1. Go to [Azure Portal → App registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) +2. **New registration** +3. Supported account types: **Personal Microsoft accounts only** (or multi-tenant) +4. Redirect URI (Web): `http://localhost:3000/api/auth/microsoft/callback` +5. Go to **API permissions** → Add: `Tasks.ReadWrite`, `User.Read`, `offline_access` +6. Copy the **Application (client) ID** + +### 2. Configure + +Create `.env.local` in the project root: ```bash -node dist/cli.js sync +TASK_SYNC_PROVIDER_A=google +TASK_SYNC_PROVIDER_B=microsoft + +TASK_SYNC_GOOGLE_CLIENT_ID=your-google-client-id +TASK_SYNC_GOOGLE_CLIENT_SECRET=your-google-client-secret + +TASK_SYNC_MS_CLIENT_ID=your-microsoft-client-id +TASK_SYNC_MS_TENANT_ID=consumers ``` -### Polling mode +### 3. Install and run ```bash -# every 5 minutes -node dist/cli.js sync --poll 5 - -# or env -export TASK_SYNC_POLL_INTERVAL_MINUTES=5 -node dist/cli.js sync +npm install +npm run web:install +npm run web:dev ``` -### Dry-run +Open [http://localhost:3000](http://localhost:3000). Click **Connect** for each +provider, approve the OAuth consent, then hit **Sync Now**. -Dry-run still uses your configured providers, but **does not write** any changes. +### Production ```bash -node dist/cli.js sync --dry-run +npm run web:build +npm run web:start ``` -## Configuration (.env) +## Quick Start — CLI -Create a `.env.local` (recommended) or `.env`: +For headless environments, scripts, or cron jobs. -### Provider selection +### 1. Install ```bash -TASK_SYNC_PROVIDER_A=google -TASK_SYNC_PROVIDER_B=microsoft +npm install +npm run build ``` -### State +### 2. Get refresh tokens + +You need refresh tokens for each provider. Helper scripts are included: ```bash -TASK_SYNC_STATE_DIR=.task-sync -TASK_SYNC_LOG_LEVEL=info +# Google +export TASK_SYNC_GOOGLE_CLIENT_ID=... +export TASK_SYNC_GOOGLE_CLIENT_SECRET=... +npm run oauth:google + +# Microsoft +export TASK_SYNC_MS_CLIENT_ID=... +export TASK_SYNC_MS_TENANT_ID=consumers +npm run oauth:microsoft ``` -### Google Tasks +Each script opens a browser for consent, then prints the refresh token. + +### 3. Configure + +Add all tokens to `.env.local`: ```bash +TASK_SYNC_PROVIDER_A=google +TASK_SYNC_PROVIDER_B=microsoft + TASK_SYNC_GOOGLE_CLIENT_ID=... TASK_SYNC_GOOGLE_CLIENT_SECRET=... TASK_SYNC_GOOGLE_REFRESH_TOKEN=... -TASK_SYNC_GOOGLE_TASKLIST_ID=@default # optional -``` -### Microsoft To Do (Graph) - -```bash TASK_SYNC_MS_CLIENT_ID=... -TASK_SYNC_MS_TENANT_ID=common # or your tenant id +TASK_SYNC_MS_TENANT_ID=consumers TASK_SYNC_MS_REFRESH_TOKEN=... -TASK_SYNC_MS_LIST_ID=... # optional (defaults to first list) ``` -Run: +### 4. Run ```bash -task-sync doctor +# Check config +node dist/cli.js doctor + +# Sync once +node dist/cli.js sync + +# Dry-run (preview changes) +node dist/cli.js sync --dry-run + +# Auto-sync every 5 minutes +node dist/cli.js sync --poll 5 + +# JSON output (for scripts) +node dist/cli.js sync --format json ``` -to see what's missing. +## Configuration -## OAuth helper scripts (refresh tokens) +All configuration is via environment variables. Create a `.env.local` file in the +project root. -These scripts spin up a local HTTP callback server, print an auth URL, and on success print the refresh token. +### Required -### Google refresh token +| Variable | Description | +|---|---| +| `TASK_SYNC_PROVIDER_A` | First provider (`google` or `microsoft`) | +| `TASK_SYNC_PROVIDER_B` | Second provider (`google` or `microsoft`) | +| `TASK_SYNC_GOOGLE_CLIENT_ID` | Google OAuth client ID | +| `TASK_SYNC_GOOGLE_CLIENT_SECRET` | Google OAuth client secret | +| `TASK_SYNC_MS_CLIENT_ID` | Microsoft app (client) ID | -1) Create OAuth credentials in Google Cloud Console: -- APIs & Services → Credentials -- Create Credentials → OAuth client ID -- Application type: **Desktop app** (recommended) -- Enable the **Google Tasks API** on the project +### Optional -2) Set env vars and run: +| Variable | Default | Description | +|---|---|---| +| `TASK_SYNC_MS_TENANT_ID` | `consumers` | Azure tenant ID | +| `TASK_SYNC_GOOGLE_REFRESH_TOKEN` | — | CLI only: Google refresh token | +| `TASK_SYNC_MS_REFRESH_TOKEN` | — | CLI only: Microsoft refresh token | +| `TASK_SYNC_GOOGLE_TASKLIST_ID` | `@default` | Google task list to sync | +| `TASK_SYNC_MS_LIST_ID` | First list | Microsoft To Do list to sync | +| `TASK_SYNC_STATE_DIR` | `.task-sync` | Directory for sync state | +| `TASK_SYNC_LOG_LEVEL` | `info` | Log level: `silent\|error\|warn\|info\|debug` | +| `TASK_SYNC_POLL_INTERVAL_MINUTES` | — | Auto-sync interval (CLI only) | +| `TASK_SYNC_MODE` | `bidirectional` | Sync mode: `bidirectional\|a-to-b-only\|mirror` | +| `TASK_SYNC_TOMBSTONE_TTL_DAYS` | `30` | How long to keep delete tombstones | -```bash -export TASK_SYNC_GOOGLE_CLIENT_ID=... -export TASK_SYNC_GOOGLE_CLIENT_SECRET=... -npm run oauth:google -``` +## How It Works -### Microsoft refresh token +### Sync State -1) Create an app registration in Azure: -- Azure Portal → App registrations → New registration -- Add a **redirect URI** (platform: *Mobile and desktop applications*): - - `http://localhost:53683/callback` -- API permissions (Delegated): - - `offline_access` - - `User.Read` - - `Tasks.ReadWrite` +`task-sync` stores local state in `.task-sync/state.json`: -2) Run: +- **`lastSyncAt`** — watermark timestamp for incremental sync +- **`mappings`** — links canonical task IDs to provider-specific IDs +- **`tombstones`** — prevents resurrecting deleted tasks -```bash -export TASK_SYNC_MS_CLIENT_ID=... -export TASK_SYNC_MS_TENANT_ID=common -npm run oauth:microsoft -``` +Delete `.task-sync/` to reset all sync state. -## How state works +### Web UI Token Storage -`task-sync` writes local state under: +The web UI stores OAuth refresh tokens in `.task-sync/tokens.json`. These +tokens never leave your machine. The web server uses them to authenticate +with Google and Microsoft APIs on your behalf during sync. -- `.task-sync/state.json` +### Sync Algorithm -This includes: +1. **Fetch** tasks from all providers (incremental via `lastSyncAt`) +2. **Cold-start match** — on first run, match tasks by title+notes to avoid duplicates +3. **Tombstone check** — skip tasks that were intentionally deleted +4. **Field-level diff** — compare each field (title, notes, status, due date) against the last canonical snapshot +5. **Conflict resolution** — if multiple providers changed the same field, last-write-wins +6. **Fan out** — apply the resolved canonical state to all providers -- `lastSyncAt` watermark (ISO timestamp) -- `mappings`: links a canonical ID to provider IDs -- `tombstones`: prevents resurrecting deleted tasks +## Project Structure -Delete `.task-sync/` to reset sync state. +``` +task-sync/ +├── src/ # Core engine + CLI +│ ├── cli.ts # CLI entry point +│ ├── sync/engine.ts # Sync algorithm +│ ├── providers/ # Google, Microsoft, Mock providers +│ ├── store/ # State persistence (JSON) +│ ├── model.ts # Task data model +│ └── ... +├── web/ # Next.js web UI +│ ├── app/ # Pages + API routes +│ │ ├── page.tsx # Dashboard +│ │ └── api/ # OAuth + sync endpoints +│ ├── components/ # React components (shadcn/ui) +│ └── lib/ # Env loading, token storage +├── test/ # Vitest tests +├── scripts/ # OAuth helper scripts +└── .env.local # Your credentials (git-ignored) +``` ## Development ```bash +# Run CLI in dev mode npm run dev -- doctor npm run dev -- sync --dry-run + +# Run web in dev mode (builds core first) +npm run web:dev + +# Tests npm test + +# Lint & typecheck npm run lint npm run typecheck ``` +## Security Notes + +- **Self-hosted only** — this project is designed to run on your own machine or server. +- **No telemetry** — no data is sent anywhere except Google and Microsoft APIs. +- **Tokens on disk** — refresh tokens are stored in `.task-sync/tokens.json`. Treat this file like a password. +- **No auth on the web UI** — if you expose the web UI to the internet, put it behind a reverse proxy with authentication (e.g., Caddy, nginx + basic auth, Cloudflare Tunnel). + ## License -MIT (see LICENSE) +MIT (see [LICENSE](LICENSE)) diff --git a/eslint.config.js b/eslint.config.js index c71d41e..1d36c7a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,7 @@ export default tseslint.config( js.configs.recommended, ...tseslint.configs.recommended, { - ignores: ['dist/**', 'node_modules/**'], + ignores: ['dist/**', 'node_modules/**', 'web/**'], }, { rules: { diff --git a/package.json b/package.json index 3cc7232..a71fb4f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,11 @@ "lint": "eslint .", "typecheck": "tsc -p tsconfig.json --noEmit", "oauth:google": "tsx scripts/google_oauth.ts", - "oauth:microsoft": "tsx scripts/microsoft_oauth.ts" + "oauth:microsoft": "tsx scripts/microsoft_oauth.ts", + "web:dev": "npm run build && npm run --prefix web dev", + "web:build": "npm run build && npm run --prefix web build", + "web:start": "npm run --prefix web start", + "web:install": "npm install --prefix web" }, "repository": { "type": "git", diff --git a/src/cli.ts b/src/cli.ts index 7b65b2d..c615e7f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -90,6 +90,8 @@ program clientId: env.TASK_SYNC_MS_CLIENT_ID!, tenantId: env.TASK_SYNC_MS_TENANT_ID!, refreshToken: env.TASK_SYNC_MS_REFRESH_TOKEN!, + // CLI uses public client flow (Mobile/Desktop platform) — no secret needed. + // The web UI uses confidential client flow (Web platform) with its own secret. listId: env.TASK_SYNC_MS_LIST_ID, }); }; diff --git a/src/config.ts b/src/config.ts index 729bd82..3ca45b7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -25,6 +25,7 @@ export const EnvSchema = z.object({ // Microsoft Graph TASK_SYNC_MS_CLIENT_ID: str.optional(), + TASK_SYNC_MS_CLIENT_SECRET: str.optional(), TASK_SYNC_MS_TENANT_ID: str.optional(), TASK_SYNC_MS_REFRESH_TOKEN: str.optional(), TASK_SYNC_MS_LIST_ID: str.optional(), diff --git a/src/extended-fields.ts b/src/extended-fields.ts new file mode 100644 index 0000000..b2f19b5 --- /dev/null +++ b/src/extended-fields.ts @@ -0,0 +1,394 @@ +/** + * Extended field handling for cross-provider sync. + * + * When syncing to a provider that doesn't natively support certain fields + * (e.g., Google Tasks lacks reminders, recurrence, categories), those fields + * are encoded as a human-readable metadata block at the end of the notes field. + * + * The block is delimited by [task-sync] ... [/task-sync] markers. + */ + +const BLOCK_START = '[task-sync]'; +const BLOCK_END = '[/task-sync]'; + +export interface ExtendedFields { + dueTime?: string; // HH:MM (24h) + reminder?: string; // ISO datetime + recurrence?: string; // RRULE-like string + categories?: string[]; // category names + importance?: string; // low | normal | high + steps?: Array<{ text: string; checked: boolean }>; // checklist items + startAt?: string; // ISO date/datetime +} + +/* ------------------------------------------------------------------ */ +/* Extract / Embed */ +/* ------------------------------------------------------------------ */ + +/** + * Extract the metadata block from a notes string. + * Returns the clean notes (without block) and the parsed fields. + */ +export function extractMetadata(notes: string): { + cleanNotes: string; + fields: ExtendedFields; +} { + const startIdx = notes.indexOf(BLOCK_START); + const endIdx = notes.indexOf(BLOCK_END); + + if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) { + return { cleanNotes: notes, fields: {} }; + } + + const before = notes.slice(0, startIdx).trimEnd(); + const after = notes.slice(endIdx + BLOCK_END.length).trimStart(); + const cleanNotes = (before + (after ? '\n' + after : '')).trim(); + + const blockContent = notes.slice(startIdx + BLOCK_START.length, endIdx).trim(); + const fields = parseBlock(blockContent); + + return { cleanNotes, fields }; +} + +function parseBlock(block: string): ExtendedFields { + const fields: ExtendedFields = {}; + const lines = block.split('\n'); + let inSteps = false; + const steps: Array<{ text: string; checked: boolean }> = []; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + if (inSteps) { + const stepMatch = trimmed.match(/^-\s*\[([ xX])\]\s*(.+)$/); + if (stepMatch) { + steps.push({ checked: stepMatch[1] !== ' ', text: stepMatch[2].trim() }); + continue; + } + inSteps = false; + } + + const colonIdx = trimmed.indexOf(':'); + if (colonIdx === -1) continue; + const key = trimmed.slice(0, colonIdx).trim().toLowerCase(); + const value = trimmed.slice(colonIdx + 1).trim(); + + switch (key) { + case 'due_time': + fields.dueTime = value; + break; + case 'reminder': + // Accept both ISO and human-readable; store as-is (provider will parse) + fields.reminder = value; + break; + case 'repeat': + case 'recurrence': + // Accept both RRULE and human-readable; try to detect RRULE + fields.recurrence = value.includes('FREQ=') ? value : parseHumanRecurrence(value) ?? value; + break; + case 'categories': + fields.categories = value.split(',').map(s => s.trim()).filter(Boolean); + break; + case 'importance': + case 'priority': + fields.importance = value.toLowerCase(); + break; + case 'start': + fields.startAt = value; + break; + case 'steps': + inSteps = true; + break; + } + } + + if (steps.length) fields.steps = steps; + return fields; +} + +/** + * Embed extended fields as a metadata block at the end of notes. + * Only includes fields that have non-default values. + * Uses human-readable labels but keeps machine-parseable values. + */ +export function embedMetadata(notes: string, fields: ExtendedFields): string { + // Strip any existing block first + const { cleanNotes } = extractMetadata(notes || ''); + + const lines: string[] = []; + + if (fields.dueTime) lines.push(`due_time: ${fields.dueTime}`); + if (fields.reminder) lines.push(`reminder: ${fields.reminder}`); + if (fields.recurrence) lines.push(`repeat: ${formatRecurrenceHuman(fields.recurrence)}`); + if (fields.categories?.length) lines.push(`categories: ${fields.categories.join(', ')}`); + if (fields.importance && fields.importance !== 'normal') lines.push(`importance: ${fields.importance}`); + if (fields.startAt) lines.push(`start: ${fields.startAt}`); + if (fields.steps?.length) { + lines.push('steps:'); + for (const step of fields.steps) { + lines.push(`- [${step.checked ? 'x' : ' '}] ${step.text}`); + } + } + + if (lines.length === 0) return cleanNotes; + + const block = `${BLOCK_START}\n${lines.join('\n')}\n${BLOCK_END}`; + return cleanNotes ? `${cleanNotes}\n\n${block}` : block; +} + +/* ------------------------------------------------------------------ */ +/* Human-readable formatting helpers */ +/* ------------------------------------------------------------------ */ + +const DAY_NAMES: Record = { + MO: 'Mon', TU: 'Tue', WE: 'Wed', TH: 'Thu', FR: 'Fri', SA: 'Sat', SU: 'Sun', +}; + +/** Format an RRULE string into a human-readable description. */ +function formatRecurrenceHuman(rule: string): string { + const parts = new Map( + rule.split(';').map(p => { + const eq = p.indexOf('='); + return (eq === -1 ? [p, ''] : [p.slice(0, eq), p.slice(eq + 1)]) as [string, string]; + }), + ); + + const freq = parts.get('FREQ') ?? ''; + const interval = Number(parts.get('INTERVAL')) || 1; + const byday = parts.get('BYDAY'); + const bymonthday = parts.get('BYMONTHDAY'); + const until = parts.get('UNTIL'); + const count = parts.get('COUNT'); + + let result = ''; + + // Frequency + const freqMap: Record = { + DAILY: ['daily', 'days'], + WEEKLY: ['weekly', 'weeks'], + MONTHLY: ['monthly', 'months'], + YEARLY: ['yearly', 'years'], + }; + const [single, plural] = freqMap[freq] ?? [freq.toLowerCase(), freq.toLowerCase()]; + result = interval === 1 ? `Every ${single.replace('ly', '')}` : `Every ${interval} ${plural}`; + if (interval === 1) { + result = freq === 'DAILY' ? 'Daily' : freq === 'WEEKLY' ? 'Weekly' : freq === 'MONTHLY' ? 'Monthly' : freq === 'YEARLY' ? 'Yearly' : result; + } + + // Days of week + if (byday) { + const days = byday.split(','); + const weekdays = ['MO', 'TU', 'WE', 'TH', 'FR']; + const weekend = ['SA', 'SU']; + if (days.length === 5 && weekdays.every(d => days.includes(d))) { + result += ' on weekdays'; + } else if (days.length === 2 && weekend.every(d => days.includes(d))) { + result += ' on weekends'; + } else { + result += ` on ${days.map(d => DAY_NAMES[d] ?? d).join(', ')}`; + } + } + + // Day of month + if (bymonthday) result += ` on day ${bymonthday}`; + + // End condition + if (until) result += ` until ${formatDateHuman(until)}`; + else if (count) result += ` (${count} times)`; + + return result; +} + +/** Format a date string (ISO or YYYY-MM-DD) to "Mon DD, YYYY". */ +function formatDateHuman(iso: string): string { + try { + const d = new Date(iso); + if (isNaN(d.getTime())) return iso; + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + return `${months[d.getUTCMonth()]} ${d.getUTCDate()}, ${d.getUTCFullYear()}`; + } catch { + return iso; + } +} + +/* ------------------------------------------------------------------ */ +/* Recurrence serialization (Microsoft Graph ↔ RRULE-like string) */ +/* ------------------------------------------------------------------ */ + +export interface GraphRecurrencePattern { + type: string; + interval: number; + daysOfWeek?: string[]; + dayOfMonth?: number; + month?: number; + firstDayOfWeek?: string; + index?: string; +} + +export interface GraphRecurrenceRange { + type: string; + startDate?: string; + endDate?: string; + numberOfOccurrences?: number; + recurrenceTimeZone?: string; +} + +export interface GraphRecurrence { + pattern: GraphRecurrencePattern; + range: GraphRecurrenceRange; +} + +const FREQ_MAP: Record = { + daily: 'DAILY', weekly: 'WEEKLY', + absoluteMonthly: 'MONTHLY', relativeMonthly: 'MONTHLY', + absoluteYearly: 'YEARLY', relativeYearly: 'YEARLY', +}; + +const DAY_TO_ABBR: Record = { + sunday: 'SU', monday: 'MO', tuesday: 'TU', wednesday: 'WE', + thursday: 'TH', friday: 'FR', saturday: 'SA', +}; + +const ABBR_TO_DAY: Record = { + SU: 'sunday', MO: 'monday', TU: 'tuesday', WE: 'wednesday', + TH: 'thursday', FR: 'friday', SA: 'saturday', +}; + +const POS_TO_STR: Record = { + first: '1', second: '2', third: '3', fourth: '4', last: '-1', +}; + +const STR_TO_POS: Record = { + '1': 'first', '2': 'second', '3': 'third', '4': 'fourth', '-1': 'last', +}; + +/** Serialize a Microsoft Graph recurrence to an RRULE-like string. */ +export function serializeRecurrence(rec: GraphRecurrence): string { + const parts: string[] = []; + const p = rec.pattern; + + parts.push(`FREQ=${FREQ_MAP[p.type] ?? p.type.toUpperCase()}`); + if (p.interval > 1) parts.push(`INTERVAL=${p.interval}`); + + if (p.daysOfWeek?.length) { + parts.push(`BYDAY=${p.daysOfWeek.map(d => DAY_TO_ABBR[d] ?? d.slice(0, 2).toUpperCase()).join(',')}`); + } + if (p.dayOfMonth && (p.type === 'absoluteMonthly' || p.type === 'absoluteYearly')) { + parts.push(`BYMONTHDAY=${p.dayOfMonth}`); + } + if (p.month && (p.type === 'absoluteYearly' || p.type === 'relativeYearly')) { + parts.push(`BYMONTH=${p.month}`); + } + if (p.index && (p.type === 'relativeMonthly' || p.type === 'relativeYearly')) { + parts.push(`BYSETPOS=${POS_TO_STR[p.index] ?? p.index}`); + } + + const r = rec.range; + if (r.type === 'endDate' && r.endDate) parts.push(`UNTIL=${r.endDate}`); + else if (r.type === 'numbered' && r.numberOfOccurrences) parts.push(`COUNT=${r.numberOfOccurrences}`); + + return parts.join(';'); +} + +/** Deserialize an RRULE-like string back to a Microsoft Graph recurrence object. */ +export function deserializeRecurrence(rule: string): GraphRecurrence | null { + if (!rule) return null; + + const parts = new Map( + rule.split(';').map(p => { + const eq = p.indexOf('='); + return (eq === -1 ? [p, ''] : [p.slice(0, eq), p.slice(eq + 1)]) as [string, string]; + }), + ); + + const freq = parts.get('FREQ')?.toLowerCase(); + if (!freq) return null; + + const interval = Number(parts.get('INTERVAL')) || 1; + const byday = parts.get('BYDAY')?.split(','); + const bymonthday = Number(parts.get('BYMONTHDAY')) || undefined; + const bymonth = Number(parts.get('BYMONTH')) || undefined; + const bysetpos = parts.get('BYSETPOS'); + const until = parts.get('UNTIL'); + const count = Number(parts.get('COUNT')) || undefined; + + let type: string; + if (freq === 'daily') type = 'daily'; + else if (freq === 'weekly') type = 'weekly'; + else if (freq === 'monthly') type = bysetpos ? 'relativeMonthly' : 'absoluteMonthly'; + else if (freq === 'yearly') type = bysetpos ? 'relativeYearly' : 'absoluteYearly'; + else type = freq; + + return { + pattern: { + type, + interval, + daysOfWeek: byday?.map(d => ABBR_TO_DAY[d] ?? d.toLowerCase()), + dayOfMonth: bymonthday, + month: bymonth, + firstDayOfWeek: 'sunday', + index: bysetpos ? STR_TO_POS[bysetpos] : undefined, + }, + range: { + type: until ? 'endDate' : count ? 'numbered' : 'noEnd', + startDate: new Date().toISOString().split('T')[0], + endDate: until, + numberOfOccurrences: count, + }, + }; +} + +/* ------------------------------------------------------------------ */ +/* Date / time helpers */ +/* ------------------------------------------------------------------ */ + +/** + * Try to parse a human-readable recurrence back to RRULE. + * This is best-effort — if we can't parse, return null and store as-is. + */ +function parseHumanRecurrence(text: string): string | null { + const lower = text.toLowerCase().trim(); + if (lower.startsWith('daily')) return 'FREQ=DAILY'; + if (lower.startsWith('weekly on weekdays')) return 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR'; + if (lower.startsWith('weekly on weekends')) return 'FREQ=WEEKLY;BYDAY=SA,SU'; + if (lower.startsWith('weekly')) { + const dayMatch = text.match(/on\s+(.+?)(\s+until|\s+\(|$)/i); + if (dayMatch) { + const nameToAbbr: Record = { + mon: 'MO', tue: 'TU', wed: 'WE', thu: 'TH', fri: 'FR', sat: 'SA', sun: 'SU', + }; + const days = dayMatch[1].split(',').map(d => nameToAbbr[d.trim().toLowerCase().slice(0, 3)] ?? '').filter(Boolean); + if (days.length) return `FREQ=WEEKLY;BYDAY=${days.join(',')}`; + } + return 'FREQ=WEEKLY'; + } + if (lower.startsWith('monthly')) return 'FREQ=MONTHLY'; + if (lower.startsWith('yearly')) return 'FREQ=YEARLY'; + return null; +} + +/** Extract HH:MM time from an ISO datetime string. Returns undefined for midnight. */ +export function extractTimeFromIso(iso: string): string | undefined { + const match = iso.match(/T(\d{2}):(\d{2})/); + if (!match) return undefined; + const time = `${match[1]}:${match[2]}`; + return time === '00:00' ? undefined : time; +} + +/** Combine a date-only ISO string with an optional HH:MM time string. */ +export function combineDateAndTime(dateIso: string, time?: string): string { + if (!time) return dateIso; + const datePart = dateIso.split('T')[0]; + return `${datePart}T${time}:00.000Z`; +} + +/** + * Normalize an ISO datetime to date-only. + * Uses noon UTC to avoid day-boundary shifts when displayed in local timezones. + * (e.g., midnight UTC shows as "previous day" in UTC-5 and later timezones.) + */ +export function normalizeDateOnly(iso: string): string { + const datePart = iso.split('T')[0]; + return `${datePart}T12:00:00.000Z`; +} diff --git a/src/http.ts b/src/http.ts index cf8e097..8b5990e 100644 --- a/src/http.ts +++ b/src/http.ts @@ -101,7 +101,8 @@ export async function requestJson( if (!res.ok) { const txt = await res.text().catch(() => undefined); const retryAfterMs = parseRetryAfterMs(res.headers.get('retry-after')); - const err = new HttpError(`HTTP ${res.status} for ${finalUrl}`, res.status, finalUrl, txt, retryAfterMs); + const detail = txt ? ` — ${txt.slice(0, 300)}` : ''; + const err = new HttpError(`HTTP ${res.status} for ${finalUrl}${detail}`, res.status, finalUrl, txt, retryAfterMs); if (attempt <= retries && isTransientStatus(res.status)) { const wait = retryAfterMs ?? backoffMs * 2 ** (attempt - 1); await sleep(wait); diff --git a/src/model.ts b/src/model.ts index 3cd7264..fa9339c 100644 --- a/src/model.ts +++ b/src/model.ts @@ -2,13 +2,36 @@ export type ProviderName = 'mockA' | 'mockB' | 'google' | 'microsoft'; export type TaskStatus = 'active' | 'completed' | 'deleted'; +export type Importance = 'low' | 'normal' | 'high'; + +export interface ChecklistItem { + text: string; + checked: boolean; +} + export interface Task { /** Provider-local id (opaque). */ id: string; title: string; notes?: string; status: TaskStatus; - dueAt?: string; // ISO + /** Due date (ISO, normalized to date-only for cross-provider compat). */ + dueAt?: string; + /** Time component of due date (HH:MM, 24h). Google Tasks is date-only, so + * the time is preserved via a metadata block in notes. */ + dueTime?: string; + /** Reminder datetime (ISO). Microsoft To Do only. */ + reminder?: string; + /** Recurrence rule (RRULE-like string). Microsoft To Do only. */ + recurrence?: string; + /** Categories / labels. Microsoft To Do only. */ + categories?: string[]; + /** Priority / importance. Microsoft To Do only. */ + importance?: Importance; + /** Checklist items / steps. Microsoft To Do only (read-only sync). */ + steps?: ChecklistItem[]; + /** Start date/time (ISO). Microsoft To Do only. */ + startAt?: string; /** * Provider-specific extra data that should round-trip without loss. * Engine treats this as opaque. diff --git a/src/providers/google.ts b/src/providers/google.ts index 44b6d44..46df930 100644 --- a/src/providers/google.ts +++ b/src/providers/google.ts @@ -1,6 +1,7 @@ -import type { Task } from '../model.js'; +import type { Task, Importance } from '../model.js'; import type { TaskProvider } from './provider.js'; import { requestJson, type FetchLike } from '../http.js'; +import { extractMetadata, embedMetadata, type ExtendedFields } from '../extended-fields.js'; export interface GoogleTasksProviderOptions { /** OAuth client id */ @@ -37,12 +38,25 @@ interface GoogleListTasksResponse { } function toCanonical(t: GoogleTask): Task { + // Extract metadata block from notes to restore extended fields + const { cleanNotes, fields } = extractMetadata(t.notes ?? ''); + return { id: t.id, title: t.title, - notes: t.notes, + notes: cleanNotes || undefined, status: t.status === 'completed' ? 'completed' : 'active', dueAt: t.due, + + // Restore extended fields from metadata block + dueTime: fields.dueTime, + reminder: fields.reminder, + recurrence: fields.recurrence, + categories: fields.categories, + importance: fields.importance as Importance | undefined, + steps: fields.steps, + startAt: fields.startAt, + updatedAt: t.updated, }; } @@ -121,9 +135,21 @@ export class GoogleTasksProvider implements TaskProvider { const tasklistId = this.opts.tasklistId ?? '@default'; const isCreate = !input.id; + // Embed extended fields (unsupported by Google Tasks) into notes + const extraFields: ExtendedFields = { + dueTime: input.dueTime, + reminder: input.reminder, + recurrence: input.recurrence, + categories: input.categories, + importance: input.importance, + steps: input.steps, + startAt: input.startAt, + }; + const notesWithMeta = embedMetadata(input.notes ?? '', extraFields); + const payload: Partial = { title: input.title, - notes: input.notes, + notes: notesWithMeta || undefined, status: input.status === 'completed' ? 'completed' : 'needsAction', due: input.dueAt, }; diff --git a/src/providers/microsoft.ts b/src/providers/microsoft.ts index af4e085..f5a2c36 100644 --- a/src/providers/microsoft.ts +++ b/src/providers/microsoft.ts @@ -1,6 +1,14 @@ -import type { Task } from '../model.js'; +import type { Task, Importance } from '../model.js'; import type { TaskProvider } from './provider.js'; import { requestJson, type FetchLike } from '../http.js'; +import { + serializeRecurrence, + deserializeRecurrence, + extractTimeFromIso, + combineDateAndTime, + normalizeDateOnly, + type GraphRecurrence, +} from '../extended-fields.js'; export interface MicrosoftTodoProviderOptions { /** Azure AD app client id */ @@ -9,6 +17,8 @@ export interface MicrosoftTodoProviderOptions { tenantId: string; /** OAuth refresh token */ refreshToken: string; + /** Client secret (required for confidential/web clients) */ + clientSecret?: string; /** Task list id (defaults to first list) */ listId?: string; /** Inject fetch for tests */ @@ -39,16 +49,36 @@ interface GraphBody { contentType: 'text' | 'html'; } +interface GraphDateTimeTimeZone { + dateTime: string; + timeZone: string; +} + +interface GraphChecklistItem { + id?: string; + displayName: string; + isChecked: boolean; +} + interface GraphTask { id: string; title: string; body?: GraphBody; - dueDateTime?: { dateTime: string; timeZone: string }; - completedDateTime?: { dateTime: string; timeZone: string }; + dueDateTime?: GraphDateTimeTimeZone; + completedDateTime?: GraphDateTimeTimeZone; /** Graph To Do supports a status field. Completed date is derived server-side. */ status?: 'notStarted' | 'inProgress' | 'completed' | 'waitingOnOthers' | 'deferred' | string; lastModifiedDateTime: string; createdDateTime: string; + + // Extended fields + reminderDateTime?: GraphDateTimeTimeZone; + isReminderOn?: boolean; + recurrence?: GraphRecurrence; + categories?: string[]; + importance?: 'low' | 'normal' | 'high'; + startDateTime?: GraphDateTimeTimeZone; + checklistItems?: GraphChecklistItem[]; } interface GraphListTasksResponse { @@ -67,7 +97,7 @@ function normalizeIsoPrecision(iso: string): string { }); } -function normalizeGraphDate(dt?: { dateTime: string; timeZone: string }): string | undefined { +function normalizeGraphDate(dt?: GraphDateTimeTimeZone): string | undefined { if (!dt?.dateTime) return undefined; // Microsoft Graph To Do uses a { dateTime, timeZone } pair. @@ -89,12 +119,34 @@ function normalizeGraphDate(dt?: { dateTime: string; timeZone: string }): string } function toCanonical(t: GraphTask): Task { + const dueFull = normalizeGraphDate(t.dueDateTime); + return { id: t.id, title: t.title, notes: t.body?.content, status: t.status === 'completed' || t.completedDateTime ? 'completed' : 'active', - dueAt: normalizeGraphDate(t.dueDateTime), + + // Split due into date-only + time for cross-provider compat + dueAt: dueFull ? normalizeDateOnly(dueFull) : undefined, + dueTime: dueFull ? extractTimeFromIso(dueFull) : undefined, + + // Extended fields + reminder: + t.isReminderOn && t.reminderDateTime + ? normalizeGraphDate(t.reminderDateTime) + : undefined, + recurrence: t.recurrence ? serializeRecurrence(t.recurrence) : undefined, + categories: t.categories?.length ? t.categories : undefined, + importance: + t.importance && t.importance !== 'normal' + ? (t.importance as Importance) + : undefined, + steps: t.checklistItems?.length + ? t.checklistItems.map((i) => ({ text: i.displayName, checked: i.isChecked })) + : undefined, + startAt: normalizeGraphDate(t.startDateTime), + updatedAt: t.lastModifiedDateTime, }; } @@ -122,6 +174,9 @@ export class MicrosoftTodoProvider implements TaskProvider { scope: 'offline_access https://graph.microsoft.com/Tasks.ReadWrite https://graph.microsoft.com/User.Read', }); + // Confidential clients (web apps) require client_secret for token refresh. + if (this.opts.clientSecret) body.set('client_secret', this.opts.clientSecret); + const url = `https://login.microsoftonline.com/${encodeURIComponent(this.opts.tenantId)}/oauth2/v2.0/token`; const res = await this.fetcher(url, { method: 'POST', @@ -173,11 +228,14 @@ export class MicrosoftTodoProvider implements TaskProvider { // OData $filter on lastModifiedDateTime so Graph only returns changed // tasks instead of the full list. let next: string | undefined; + const base = `/me/todo/lists/${encodeURIComponent(listId)}/tasks`; + const expand = '$expand=checklistItems'; + if (since) { const filter = `lastModifiedDateTime ge ${since}`; - next = `/me/todo/lists/${encodeURIComponent(listId)}/tasks?$top=100&$filter=${encodeURIComponent(filter)}`; + next = `${base}?$top=100&${expand}&$filter=${encodeURIComponent(filter)}`; } else { - next = `/me/todo/lists/${encodeURIComponent(listId)}/tasks?$top=100`; + next = `${base}?$top=100&${expand}`; } while (next) { @@ -194,24 +252,54 @@ export class MicrosoftTodoProvider implements TaskProvider { const listId = await this.getListId(); const isCreate = !input.id; - const payload: Partial & { body?: GraphBody } = { + // Reconstruct full due datetime from date + time components + const dueIso = input.dueAt + ? combineDateAndTime(input.dueAt, input.dueTime) + : undefined; + + // Core fields — always included + const payload: Record = { title: input.title, body: input.notes - ? { - contentType: 'text', - content: input.notes, - } + ? { contentType: 'text', content: input.notes } : undefined, - dueDateTime: input.dueAt - ? { - dateTime: input.dueAt, - timeZone: 'UTC', - } + dueDateTime: dueIso + ? { dateTime: dueIso, timeZone: 'UTC' } : undefined, // Graph expects status mutations, not completedDateTime writes. status: input.status === 'completed' ? 'completed' : 'notStarted', }; + // Extended fields — only include if they have values. + // For PATCH (updates), omitting a field means "don't change it". + // This avoids overwriting server-managed fields like recurrence + // with reconstructed values that may differ in startDate, etc. + if (input.reminder) { + payload.isReminderOn = true; + payload.reminderDateTime = { dateTime: input.reminder, timeZone: 'UTC' }; + } + if (input.categories?.length) { + payload.categories = input.categories; + } + if (input.importance) { + payload.importance = input.importance; + } + if (input.startAt) { + payload.startDateTime = { dateTime: input.startAt, timeZone: 'UTC' }; + } + + // Recurrence: only set on CREATE. For PATCH, let Microsoft manage it + // to avoid conflicts with server-side recurrence state. + if (isCreate && input.recurrence) { + const rec = deserializeRecurrence(input.recurrence); + if (rec) payload.recurrence = rec; + } + + // Remove undefined values (don't send to API) + for (const k of Object.keys(payload)) { + if (payload[k] === undefined) delete payload[k]; + } + const res = isCreate ? await this.api(`/me/todo/lists/${encodeURIComponent(listId)}/tasks`, { method: 'POST', diff --git a/src/sync/engine.ts b/src/sync/engine.ts index c2de0d8..446c8a2 100644 --- a/src/sync/engine.ts +++ b/src/sync/engine.ts @@ -1,5 +1,5 @@ import { appendFile } from 'node:fs/promises'; -import type { ProviderName, Task } from '../model.js'; +import type { ProviderName, Task, TaskStatus, Importance, ChecklistItem } from '../model.js'; import type { TaskProvider } from '../providers/provider.js'; import { JsonStore, type MappingRecord } from '../store/jsonStore.js'; import { acquireLock } from '../store/lock.js'; @@ -30,7 +30,7 @@ export interface SyncAction { export interface SyncConflict { canonicalId: string; - field: 'title' | 'notes' | 'dueAt' | 'status'; + field: string; providers: Array<{ provider: string; id: string; updatedAt: string; value: unknown }>; winner: { provider: string; id: string; updatedAt: string }; overwritten: Array<{ provider: string; id: string }>; @@ -69,25 +69,22 @@ function normField(s?: string): string { } /** - * Normalize an ISO timestamp for comparison. - * Truncates fractional seconds to milliseconds so that providers returning - * different precisions (e.g. ".000Z" vs ".0000000Z") compare as equal. + * Semantic equality for a task field. Handles undefined/empty normalization, + * ISO timestamp precision differences, and complex types (arrays, objects). */ -function normIso(s?: string): string { - if (!s) return ''; - return s.replace(/\.(\d+)Z$/, (_match, frac: string) => { - const ms = (frac + '000').slice(0, 3); - return `.${ms}Z`; - }); -} - -/** - * Semantic equality for a task field. Handles undefined/empty normalization - * and ISO timestamp precision differences. - */ -function fieldEqual(field: 'title' | 'notes' | 'dueAt' | 'status', a?: string, b?: string): boolean { - if (field === 'dueAt') return normIso(a) === normIso(b); - if (field === 'notes') return normField(a) === normField(b); +function fieldEqual(field: string, a: unknown, b: unknown): boolean { + // Date-only fields: compare YYYY-MM-DD part only (ignore time, avoids noon-vs-midnight mismatches) + if (field === 'dueAt' || field === 'startAt') { + const da = ((a as string) ?? '').split('T')[0]; + const db = ((b as string) ?? '').split('T')[0]; + return da === db; + } + if (field === 'notes') return normField(a as string) === normField(b as string); + // Array / object fields: compare by JSON (order-sensitive but consistent per-provider) + if (field === 'categories' || field === 'steps') { + return JSON.stringify(a ?? null) === JSON.stringify(b ?? null); + } + // String fields (title, status, dueTime, reminder, recurrence, importance) return (a ?? '') === (b ?? ''); } @@ -381,7 +378,10 @@ export class SyncEngine { if (byProvTask.size === 0) continue; type CanonicalData = Omit; - const fields = ['title', 'notes', 'dueAt', 'status'] as const; + const fields = [ + 'title', 'notes', 'dueAt', 'status', + 'dueTime', 'reminder', 'recurrence', 'categories', 'importance', 'steps', 'startAt', + ] as const; type Field = (typeof fields)[number]; const firstTask = [...byProvTask.values()][0]!; @@ -390,7 +390,14 @@ export class SyncEngine { title: baseline?.title ?? firstTask.title, notes: baseline?.notes, dueAt: baseline?.dueAt, + dueTime: baseline?.dueTime, status: baseline?.status ?? firstTask.status, + reminder: baseline?.reminder, + recurrence: baseline?.recurrence, + categories: baseline?.categories, + importance: baseline?.importance, + steps: baseline?.steps, + startAt: baseline?.startAt, metadata: baseline?.metadata, updatedAt: baseline?.updatedAt ?? firstTask.updatedAt, }; @@ -402,7 +409,7 @@ export class SyncEngine { for (const f of fields) { const baseVal = baseline ? baseline[f] : undefined; const val = t[f]; - if (!fieldEqual(f, baseVal as string | undefined, val as string | undefined)) set.add(f); + if (!fieldEqual(f, baseVal, val)) set.add(f); } if (set.size) changedBy.set(prov, set); } @@ -414,20 +421,19 @@ export class SyncEngine { if (set.has(f)) contenders.push({ prov, t: byProvTask.get(prov)! }); } - const assign = (field: Field, val: Task[Field]) => { + const assign = (field: Field, val: unknown) => { switch (field) { - case 'title': - canonical.title = val as Task['title']; - break; - case 'notes': - canonical.notes = val as Task['notes']; - break; - case 'dueAt': - canonical.dueAt = val as Task['dueAt']; - break; - case 'status': - canonical.status = val as Task['status']; - break; + case 'title': canonical.title = val as string; break; + case 'notes': canonical.notes = val as string | undefined; break; + case 'dueAt': canonical.dueAt = val as string | undefined; break; + case 'status': canonical.status = val as TaskStatus; break; + case 'dueTime': canonical.dueTime = val as string | undefined; break; + case 'reminder': canonical.reminder = val as string | undefined; break; + case 'recurrence': canonical.recurrence = val as string | undefined; break; + case 'categories': canonical.categories = val as string[] | undefined; break; + case 'importance': canonical.importance = val as Importance | undefined; break; + case 'steps': canonical.steps = val as ChecklistItem[] | undefined; break; + case 'startAt': canonical.startAt = val as string | undefined; break; } }; @@ -491,7 +497,14 @@ export class SyncEngine { title: canonical.title, notes: canonical.notes, dueAt: canonical.dueAt, + dueTime: canonical.dueTime, status: canonical.status, + reminder: canonical.reminder, + recurrence: canonical.recurrence, + categories: canonical.categories, + importance: canonical.importance, + steps: canonical.steps, + startAt: canonical.startAt, metadata: canonical.metadata, updatedAt: canonical.updatedAt, }); @@ -525,7 +538,14 @@ export class SyncEngine { title: canonical.title, notes: canonical.notes, dueAt: canonical.dueAt, + dueTime: canonical.dueTime, status: canonical.status, + reminder: canonical.reminder, + recurrence: canonical.recurrence, + categories: canonical.categories, + importance: canonical.importance, + steps: canonical.steps, + startAt: canonical.startAt, metadata: canonical.metadata, updatedAt: canonical.updatedAt, }); @@ -542,11 +562,9 @@ export class SyncEngine { } // Update only if any field differs (using semantic comparison). - const differs = - !fieldEqual('title', existing.title, canonical.title) || - !fieldEqual('notes', existing.notes, canonical.notes) || - !fieldEqual('dueAt', existing.dueAt, canonical.dueAt) || - !fieldEqual('status', existing.status, canonical.status); + const differs = fields.some( + (f) => !fieldEqual(f, existing[f as keyof Task], canonical[f as keyof CanonicalData]), + ); if (!differs) { push({ @@ -566,7 +584,7 @@ export class SyncEngine { source: { provider: 'canonical', id: m.canonicalId }, target: { provider: target.name, id: targetId }, title: canonical.title, - detail: `field-level update (title/notes/status/dueAt)`, + detail: `field-level update`, }); if (!dryRun) { @@ -576,7 +594,14 @@ export class SyncEngine { title: canonical.title, notes: canonical.notes, dueAt: canonical.dueAt, + dueTime: canonical.dueTime, status: canonical.status, + reminder: canonical.reminder, + recurrence: canonical.recurrence, + categories: canonical.categories, + importance: canonical.importance, + steps: canonical.steps, + startAt: canonical.startAt, metadata: canonical.metadata ?? existing.metadata, updatedAt: canonical.updatedAt, }); diff --git a/test/microsoftProvider.test.ts b/test/microsoftProvider.test.ts index c7dc263..a2137a7 100644 --- a/test/microsoftProvider.test.ts +++ b/test/microsoftProvider.test.ts @@ -59,7 +59,7 @@ describe('MicrosoftTodoProvider', () => { title: 'Hi', notes: 'B', status: 'active', - dueAt: '2026-02-10T00:00:00.000Z', + dueAt: '2026-02-10T12:00:00.000Z', updatedAt: '2026-02-06T00:00:00.000Z', }); }); diff --git a/test/web.test.ts b/test/web.test.ts new file mode 100644 index 0000000..385819a --- /dev/null +++ b/test/web.test.ts @@ -0,0 +1,186 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { ChildProcess, spawn } from 'node:child_process'; +import path from 'node:path'; +import { writeFile, mkdir, rm, readFile } from 'node:fs/promises'; + +const WEB_DIR = path.resolve(import.meta.dirname, '..', 'web'); +const STATE_DIR = path.resolve(import.meta.dirname, '..', '.task-sync-test-web'); +const PORT = 3099; +const BASE = `http://localhost:${PORT}`; + +let server: ChildProcess; + +/** + * End-to-end tests for the web UI API routes. + * + * These tests start a Next.js production server and exercise the API + * without real OAuth credentials — they validate routing, error handling, + * token storage, and status reporting. + */ + +async function waitForServer(url: string, timeoutMs = 60_000): Promise { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + try { + const res = await fetch(url); + if (res.ok || res.status < 500) return; + } catch { + // server not ready yet + } + await new Promise((r) => setTimeout(r, 500)); + } + throw new Error(`Server at ${url} did not become ready within ${timeoutMs}ms`); +} + +beforeAll(async () => { + // Create test state directory + await mkdir(STATE_DIR, { recursive: true }); + + // Create a minimal .env.local for testing (no real credentials) + const envContent = [ + 'TASK_SYNC_GOOGLE_CLIENT_ID=test-google-id', + 'TASK_SYNC_GOOGLE_CLIENT_SECRET=test-google-secret', + 'TASK_SYNC_MS_CLIENT_ID=test-ms-id', + 'TASK_SYNC_MS_TENANT_ID=consumers', + `TASK_SYNC_STATE_DIR=${STATE_DIR}`, + ].join('\n'); + + // Write to project root .env (the web app reads from parent) + await writeFile(path.resolve(import.meta.dirname, '..', '.env.test'), envContent); + + // Start the production server + server = spawn('npx', ['next', 'start', '-p', String(PORT)], { + cwd: WEB_DIR, + env: { + ...process.env, + NODE_ENV: 'production', + TASK_SYNC_GOOGLE_CLIENT_ID: 'test-google-id', + TASK_SYNC_GOOGLE_CLIENT_SECRET: 'test-google-secret', + TASK_SYNC_MS_CLIENT_ID: 'test-ms-id', + TASK_SYNC_MS_TENANT_ID: 'consumers', + TASK_SYNC_STATE_DIR: STATE_DIR, + }, + stdio: 'pipe', + }); + + await waitForServer(`${BASE}/api/status`); +}, 60_000); + +afterAll(async () => { + server?.kill('SIGTERM'); + // Wait for process to exit + await new Promise((r) => setTimeout(r, 1000)); + // Cleanup + await rm(STATE_DIR, { recursive: true, force: true }).catch(() => {}); + await rm(path.resolve(import.meta.dirname, '..', '.env.test'), { force: true }).catch( + () => {}, + ); +}); + +describe('Web API — /api/status', () => { + it('returns configuration and connection status', async () => { + const res = await fetch(`${BASE}/api/status`); + expect(res.status).toBe(200); + + const data = (await res.json()) as { configured: { google: boolean; microsoft: boolean }; connected: { google: boolean; microsoft: boolean }; lastSync: unknown }; + expect(data).toHaveProperty('configured'); + expect(data).toHaveProperty('connected'); + expect(data.configured.google).toBe(true); + expect(data.configured.microsoft).toBe(true); + expect(data.connected.google).toBe(false); + expect(data.connected.microsoft).toBe(false); + expect(data.lastSync).toBeNull(); + }); +}); + +describe('Web API — /api/auth', () => { + it('GET /api/auth/google redirects to Google consent', async () => { + const res = await fetch(`${BASE}/api/auth/google`, { redirect: 'manual' }); + expect(res.status).toBe(307); + const location = res.headers.get('location') ?? ''; + expect(location).toContain('accounts.google.com'); + expect(location).toContain('client_id=test-google-id'); + expect(location).toContain('scope='); + }); + + it('GET /api/auth/microsoft redirects to Microsoft consent', async () => { + const res = await fetch(`${BASE}/api/auth/microsoft`, { redirect: 'manual' }); + expect(res.status).toBe(307); + const location = res.headers.get('location') ?? ''; + expect(location).toContain('login.microsoftonline.com'); + expect(location).toContain('client_id=test-ms-id'); + }); + + it('GET /api/auth/google/callback with error param redirects with error', async () => { + const res = await fetch(`${BASE}/api/auth/google/callback?error=access_denied`, { + redirect: 'manual', + }); + expect(res.status).toBe(307); + const location = res.headers.get('location') ?? ''; + expect(location).toContain('error=google_access_denied'); + }); + + it('GET /api/auth/microsoft/callback with error param redirects with error', async () => { + const res = await fetch( + `${BASE}/api/auth/microsoft/callback?error=access_denied`, + { redirect: 'manual' }, + ); + expect(res.status).toBe(307); + const location = res.headers.get('location') ?? ''; + expect(location).toContain('error=microsoft_access_denied'); + }); +}); + +describe('Web API — /api/sync', () => { + it('returns error when providers are not connected', async () => { + const res = await fetch(`${BASE}/api/sync`, { method: 'POST' }); + expect(res.status).toBe(400); + const data = (await res.json()) as { error: string }; + expect(data.error).toContain('connected'); + }); +}); + +describe('Web API — /api/disconnect', () => { + it('DELETE /api/disconnect/google succeeds even with no token', async () => { + const res = await fetch(`${BASE}/api/disconnect/google`, { method: 'DELETE' }); + expect(res.status).toBe(200); + const data = (await res.json()) as { ok: boolean }; + expect(data.ok).toBe(true); + }); + + it('DELETE /api/disconnect/invalid returns 400', async () => { + const res = await fetch(`${BASE}/api/disconnect/invalid`, { method: 'DELETE' }); + expect(res.status).toBe(400); + }); + + it('token storage round-trip works', async () => { + // Simulate saving a token by writing directly + const tokensPath = path.join(STATE_DIR, 'tokens.json'); + await writeFile( + tokensPath, + JSON.stringify({ google: { refreshToken: 'test-token' } }), + ); + + // Status should now show google as connected + const statusRes = await fetch(`${BASE}/api/status`); + const status = (await statusRes.json()) as { connected: { google: boolean } }; + expect(status.connected.google).toBe(true); + + // Disconnect + await fetch(`${BASE}/api/disconnect/google`, { method: 'DELETE' }); + + // Verify token was removed + const raw = await readFile(tokensPath, 'utf8'); + const tokens = JSON.parse(raw); + expect(tokens.google).toBeUndefined(); + }); +}); + +describe('Web UI — page', () => { + it('serves the dashboard HTML', async () => { + const res = await fetch(BASE); + expect(res.status).toBe(200); + const html = await res.text(); + expect(html).toContain('Task Sync'); + }); +}); diff --git a/tsup.config.ts b/tsup.config.ts index 6ea1687..b730491 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,14 +1,35 @@ import { defineConfig } from 'tsup'; +import { readFileSync, writeFileSync } from 'node:fs'; export default defineConfig({ - entry: ['src/cli.ts'], + entry: [ + 'src/cli.ts', + 'src/sync/engine.ts', + 'src/store/jsonStore.ts', + 'src/store/lock.ts', + 'src/providers/google.ts', + 'src/providers/microsoft.ts', + 'src/providers/mock.ts', + 'src/providers/provider.ts', + 'src/model.ts', + 'src/http.ts', + 'src/config.ts', + 'src/log.ts', + 'src/env.ts', + ], format: ['esm'], platform: 'node', target: 'node22', sourcemap: true, clean: true, dts: true, - banner: { - js: '#!/usr/bin/env node', + splitting: true, + async onSuccess() { + // Add shebang only to the CLI entry point + const cliPath = 'dist/cli.js'; + const content = readFileSync(cliPath, 'utf8'); + if (!content.startsWith('#!')) { + writeFileSync(cliPath, '#!/usr/bin/env node\n' + content); + } }, }); diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..b4c1abd --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,16 @@ +# dependencies +node_modules/ + +# next.js +/.next/ +/out/ + +# misc +*.pem + +# debug +npm-debug.log* + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/web/app/api/auth/google/callback/route.ts b/web/app/api/auth/google/callback/route.ts new file mode 100644 index 0000000..25696ea --- /dev/null +++ b/web/app/api/auth/google/callback/route.ts @@ -0,0 +1,59 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getConfig } from '@/lib/env'; +import { saveProviderToken } from '@/lib/tokens'; + +export async function GET(request: NextRequest) { + const config = getConfig(); + const url = new URL(request.url); + const code = url.searchParams.get('code'); + const error = url.searchParams.get('error'); + + if (error) { + return NextResponse.redirect(`${url.origin}/?error=google_${error}`); + } + + if (!code) { + return NextResponse.redirect(`${url.origin}/?error=google_missing_code`); + } + + const redirectUri = `${url.origin}/api/auth/google/callback`; + + const body = new URLSearchParams({ + code, + client_id: config.googleClientId, + client_secret: config.googleClientSecret, + redirect_uri: redirectUri, + grant_type: 'authorization_code', + }); + + const res = await fetch('https://oauth2.googleapis.com/token', { + method: 'POST', + headers: { 'content-type': 'application/x-www-form-urlencoded' }, + body, + }); + + if (!res.ok) { + const txt = await res.text().catch(() => ''); + console.error('Google token exchange failed:', res.status, txt); + return NextResponse.redirect(`${url.origin}/?error=google_token_exchange`); + } + + const json = (await res.json()) as { + access_token: string; + expires_in: number; + refresh_token?: string; + }; + + if (!json.refresh_token) { + console.error('Google did not return a refresh_token. Ensure prompt=consent and access_type=offline.'); + return NextResponse.redirect(`${url.origin}/?error=google_no_refresh_token`); + } + + await saveProviderToken('google', { + refreshToken: json.refresh_token, + accessToken: json.access_token, + expiresAt: Date.now() + json.expires_in * 1000, + }); + + return NextResponse.redirect(`${url.origin}/?connected=google`); +} diff --git a/web/app/api/auth/google/route.ts b/web/app/api/auth/google/route.ts new file mode 100644 index 0000000..c6ab799 --- /dev/null +++ b/web/app/api/auth/google/route.ts @@ -0,0 +1,26 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getConfig } from '@/lib/env'; + +export async function GET(request: NextRequest) { + const config = getConfig(); + + if (!config.googleClientId || !config.googleClientSecret) { + return NextResponse.json( + { error: 'Google OAuth not configured. Set TASK_SYNC_GOOGLE_CLIENT_ID and TASK_SYNC_GOOGLE_CLIENT_SECRET.' }, + { status: 400 }, + ); + } + + const url = new URL(request.url); + const redirectUri = `${url.origin}/api/auth/google/callback`; + + const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth'); + authUrl.searchParams.set('client_id', config.googleClientId); + authUrl.searchParams.set('redirect_uri', redirectUri); + authUrl.searchParams.set('response_type', 'code'); + authUrl.searchParams.set('scope', 'https://www.googleapis.com/auth/tasks'); + authUrl.searchParams.set('access_type', 'offline'); + authUrl.searchParams.set('prompt', 'consent'); + + return NextResponse.redirect(authUrl.toString()); +} diff --git a/web/app/api/auth/microsoft/callback/route.ts b/web/app/api/auth/microsoft/callback/route.ts new file mode 100644 index 0000000..3e23ff2 --- /dev/null +++ b/web/app/api/auth/microsoft/callback/route.ts @@ -0,0 +1,71 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getConfig } from '@/lib/env'; +import { saveProviderToken } from '@/lib/tokens'; + +export async function GET(request: NextRequest) { + const config = getConfig(); + const url = new URL(request.url); + const code = url.searchParams.get('code'); + const error = url.searchParams.get('error'); + + if (error) { + return NextResponse.redirect(`${url.origin}/?error=microsoft_${error}`); + } + + if (!code) { + return NextResponse.redirect(`${url.origin}/?error=microsoft_missing_code`); + } + + const redirectUri = `${url.origin}/api/auth/microsoft/callback`; + + const scopes = [ + 'offline_access', + 'https://graph.microsoft.com/User.Read', + 'https://graph.microsoft.com/Tasks.ReadWrite', + ]; + + const body = new URLSearchParams({ + client_id: config.msClientId, + grant_type: 'authorization_code', + code, + redirect_uri: redirectUri, + scope: scopes.join(' '), + }); + + // Confidential clients (web apps) require client_secret for token exchange. + if (config.msClientSecret) body.set('client_secret', config.msClientSecret); + + const res = await fetch( + `https://login.microsoftonline.com/${encodeURIComponent(config.msTenantId)}/oauth2/v2.0/token`, + { + method: 'POST', + headers: { 'content-type': 'application/x-www-form-urlencoded' }, + body, + }, + ); + + if (!res.ok) { + const txt = await res.text().catch(() => ''); + console.error('Microsoft token exchange failed:', res.status, txt); + return NextResponse.redirect(`${url.origin}/?error=microsoft_token_exchange`); + } + + const json = (await res.json()) as { + access_token: string; + expires_in: number; + refresh_token?: string; + }; + + if (!json.refresh_token) { + console.error('Microsoft did not return a refresh_token. Ensure offline_access scope is requested.'); + return NextResponse.redirect(`${url.origin}/?error=microsoft_no_refresh_token`); + } + + await saveProviderToken('microsoft', { + refreshToken: json.refresh_token, + accessToken: json.access_token, + expiresAt: Date.now() + json.expires_in * 1000, + }); + + return NextResponse.redirect(`${url.origin}/?connected=microsoft`); +} diff --git a/web/app/api/auth/microsoft/route.ts b/web/app/api/auth/microsoft/route.ts new file mode 100644 index 0000000..6c80b09 --- /dev/null +++ b/web/app/api/auth/microsoft/route.ts @@ -0,0 +1,33 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getConfig } from '@/lib/env'; + +export async function GET(request: NextRequest) { + const config = getConfig(); + + if (!config.msClientId) { + return NextResponse.json( + { error: 'Microsoft OAuth not configured. Set TASK_SYNC_MS_CLIENT_ID.' }, + { status: 400 }, + ); + } + + const url = new URL(request.url); + const redirectUri = `${url.origin}/api/auth/microsoft/callback`; + + const scopes = [ + 'offline_access', + 'https://graph.microsoft.com/User.Read', + 'https://graph.microsoft.com/Tasks.ReadWrite', + ]; + + const authUrl = new URL( + `https://login.microsoftonline.com/${encodeURIComponent(config.msTenantId)}/oauth2/v2.0/authorize`, + ); + authUrl.searchParams.set('client_id', config.msClientId); + authUrl.searchParams.set('redirect_uri', redirectUri); + authUrl.searchParams.set('response_type', 'code'); + authUrl.searchParams.set('response_mode', 'query'); + authUrl.searchParams.set('scope', scopes.join(' ')); + + return NextResponse.redirect(authUrl.toString()); +} diff --git a/web/app/api/disconnect/[provider]/route.ts b/web/app/api/disconnect/[provider]/route.ts new file mode 100644 index 0000000..172eb44 --- /dev/null +++ b/web/app/api/disconnect/[provider]/route.ts @@ -0,0 +1,16 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { deleteProviderToken } from '@/lib/tokens'; + +export async function DELETE( + _request: NextRequest, + { params }: { params: Promise<{ provider: string }> }, +) { + const { provider } = await params; + + if (provider !== 'google' && provider !== 'microsoft') { + return NextResponse.json({ error: 'Invalid provider' }, { status: 400 }); + } + + await deleteProviderToken(provider); + return NextResponse.json({ ok: true }); +} diff --git a/web/app/api/status/route.ts b/web/app/api/status/route.ts new file mode 100644 index 0000000..5396a93 --- /dev/null +++ b/web/app/api/status/route.ts @@ -0,0 +1,40 @@ +import { NextResponse } from 'next/server'; +import { getConfig, isGoogleConfigured, isMicrosoftConfigured } from '@/lib/env'; +import { readTokens } from '@/lib/tokens'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; + +export const dynamic = 'force-dynamic'; + +export async function GET() { + const config = getConfig(); + const tokens = await readTokens(); + + // Read last sync state + let lastSync: { at: string; mappings: number } | null = null; + try { + const statePath = path.join(config.stateDir, 'state.json'); + const raw = await readFile(statePath, 'utf8'); + const state = JSON.parse(raw) as { lastSyncAt?: string; mappings?: unknown[] }; + if (state.lastSyncAt) { + lastSync = { + at: state.lastSyncAt, + mappings: Array.isArray(state.mappings) ? state.mappings.length : 0, + }; + } + } catch { + // No state file yet — first run + } + + return NextResponse.json({ + configured: { + google: isGoogleConfigured(), + microsoft: isMicrosoftConfigured(), + }, + connected: { + google: !!tokens.google?.refreshToken, + microsoft: !!tokens.microsoft?.refreshToken, + }, + lastSync, + }); +} diff --git a/web/app/api/sync/route.ts b/web/app/api/sync/route.ts new file mode 100644 index 0000000..13d50eb --- /dev/null +++ b/web/app/api/sync/route.ts @@ -0,0 +1,99 @@ +import { NextResponse } from 'next/server'; +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { getConfig } from '@/lib/env'; +import { readTokens } from '@/lib/tokens'; + +/** + * Load core engine modules at runtime from the pre-built dist/. + * + * We use native dynamic import (webpackIgnore) so that Next.js does not try + * to bundle the core — it is compiled separately by tsup. + */ +async function loadCore() { + const distDir = path.resolve(process.cwd(), '..', 'dist'); + const toUrl = (name: string) => + pathToFileURL(path.join(distDir, name)).href; + + const engineUrl = toUrl('sync/engine.js'); + const storeUrl = toUrl('store/jsonStore.js'); + const googleUrl = toUrl('providers/google.js'); + const microsoftUrl = toUrl('providers/microsoft.js'); + + const [engine, store, google, microsoft] = await Promise.all([ + import(/* webpackIgnore: true */ engineUrl), + import(/* webpackIgnore: true */ storeUrl), + import(/* webpackIgnore: true */ googleUrl), + import(/* webpackIgnore: true */ microsoftUrl), + ]); + + return { + SyncEngine: engine.SyncEngine, + JsonStore: store.JsonStore, + GoogleTasksProvider: google.GoogleTasksProvider, + MicrosoftTodoProvider: microsoft.MicrosoftTodoProvider, + }; +} + +export async function POST() { + const config = getConfig(); + const tokens = await readTokens(); + + if (!tokens.google?.refreshToken || !tokens.microsoft?.refreshToken) { + return NextResponse.json( + { error: 'Both providers must be connected before syncing.' }, + { status: 400 }, + ); + } + + if (!config.googleClientId || !config.googleClientSecret) { + return NextResponse.json( + { error: 'Google OAuth credentials not configured.' }, + { status: 400 }, + ); + } + + if (!config.msClientId) { + return NextResponse.json( + { error: 'Microsoft OAuth credentials not configured.' }, + { status: 400 }, + ); + } + + try { + const { SyncEngine, JsonStore, GoogleTasksProvider, MicrosoftTodoProvider } = + await loadCore(); + + const store = new JsonStore(config.stateDir); + const engine = new SyncEngine(store); + + const providers = [ + new GoogleTasksProvider({ + clientId: config.googleClientId, + clientSecret: config.googleClientSecret, + refreshToken: tokens.google.refreshToken, + tasklistId: config.googleTasklistId, + }), + new MicrosoftTodoProvider({ + clientId: config.msClientId, + tenantId: config.msTenantId, + refreshToken: tokens.microsoft.refreshToken, + clientSecret: config.msClientSecret || undefined, + listId: config.msListId, + }), + ]; + + const report = await engine.syncMany(providers, { + mode: 'bidirectional', + tombstoneTtlDays: 30, + }); + + return NextResponse.json(report); + } catch (e) { + console.error('Sync error:', e); + return NextResponse.json( + { error: e instanceof Error ? e.message : String(e) }, + { status: 500 }, + ); + } +} diff --git a/web/app/globals.css b/web/app/globals.css new file mode 100644 index 0000000..516679f --- /dev/null +++ b/web/app/globals.css @@ -0,0 +1,124 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@import "shadcn/tailwind.css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + --radius-3xl: calc(var(--radius) + 12px); + --radius-4xl: calc(var(--radius) + 16px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/web/app/layout.tsx b/web/app/layout.tsx new file mode 100644 index 0000000..d3f7f89 --- /dev/null +++ b/web/app/layout.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import { Toaster } from '@/components/ui/sonner'; +import './globals.css'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata: Metadata = { + title: 'Task Sync', + description: 'Sync tasks between Google Tasks and Microsoft To Do', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + + ); +} diff --git a/web/app/page.tsx b/web/app/page.tsx new file mode 100644 index 0000000..ca1a9a2 --- /dev/null +++ b/web/app/page.tsx @@ -0,0 +1,5 @@ +import { Dashboard } from '@/components/dashboard'; + +export default function Home() { + return ; +} diff --git a/web/components.json b/web/components.json new file mode 100644 index 0000000..f87021e --- /dev/null +++ b/web/components.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/web/components/dashboard.tsx b/web/components/dashboard.tsx new file mode 100644 index 0000000..90b4942 --- /dev/null +++ b/web/components/dashboard.tsx @@ -0,0 +1,430 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Separator } from '@/components/ui/separator'; +import { toast } from 'sonner'; +import { + RefreshCw, + Link, + Unlink, + CheckCircle, + AlertCircle, + ArrowRightLeft, + Settings, +} from 'lucide-react'; + +/* ------------------------------------------------------------------ */ +/* Types */ +/* ------------------------------------------------------------------ */ + +interface Status { + configured: { google: boolean; microsoft: boolean }; + connected: { google: boolean; microsoft: boolean }; + lastSync: { at: string; mappings: number } | null; +} + +interface SyncReport { + dryRun: boolean; + providers: string[]; + lastSyncAt?: string; + newLastSyncAt: string; + counts: Record; + actions: Array<{ + kind: string; + executed: boolean; + source: { provider: string; id: string }; + target: { provider: string; id?: string }; + title?: string; + detail: string; + }>; + conflicts: unknown[]; + errors: Array<{ provider: string; stage: string; error: string }>; + durationMs: number; +} + +/* ------------------------------------------------------------------ */ +/* Dashboard */ +/* ------------------------------------------------------------------ */ + +export function Dashboard() { + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(true); + const [syncing, setSyncing] = useState(false); + const [lastReport, setLastReport] = useState(null); + const [error, setError] = useState(null); + + const fetchStatus = useCallback(async () => { + try { + const res = await fetch('/api/status'); + if (!res.ok) throw new Error('Failed to fetch status'); + const data: Status = await res.json(); + setStatus(data); + setError(null); + } catch { + setError('Failed to load status. Is the server running?'); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchStatus(); + + // Handle OAuth redirect results via URL params + const params = new URLSearchParams(window.location.search); + const connected = params.get('connected'); + const err = params.get('error'); + if (connected) { + toast.success( + `Connected to ${connected === 'google' ? 'Google Tasks' : 'Microsoft To Do'}`, + ); + window.history.replaceState({}, '', '/'); + } + if (err) { + toast.error(`Connection failed: ${err.replace(/_/g, ' ')}`); + window.history.replaceState({}, '', '/'); + } + }, [fetchStatus]); + + /* Sync ----------------------------------------------------------- */ + + const handleSync = async () => { + setSyncing(true); + setLastReport(null); + try { + const res = await fetch('/api/sync', { method: 'POST' }); + const data = await res.json(); + if (!res.ok) { + toast.error(data.error || 'Sync failed'); + } else { + setLastReport(data as SyncReport); + toast.success(`Synced in ${(data as SyncReport).durationMs}ms`); + fetchStatus(); + } + } catch { + toast.error('Sync request failed'); + } finally { + setSyncing(false); + } + }; + + /* Disconnect ------------------------------------------------------ */ + + const handleDisconnect = async (provider: 'google' | 'microsoft') => { + try { + await fetch(`/api/disconnect/${provider}`, { method: 'DELETE' }); + toast.success( + `Disconnected from ${provider === 'google' ? 'Google Tasks' : 'Microsoft To Do'}`, + ); + fetchStatus(); + } catch { + toast.error('Failed to disconnect'); + } + }; + + /* Loading / Error ------------------------------------------------- */ + + if (loading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ + + {error} + +
+ ); + } + + const neitherConfigured = + !status?.configured.google && !status?.configured.microsoft; + const bothConnected = + status?.connected.google && status?.connected.microsoft; + + /* Render ---------------------------------------------------------- */ + + return ( +
+ {/* Header */} +
+
+ +

Task Sync

+
+

+ Keep your Google Tasks and Microsoft To Do in sync. +

+
+ + {/* Setup guide when nothing is configured */} + {neitherConfigured && ( + + + +

Setup required

+

+ Create OAuth credentials and add them to{' '} + + .env.local + {' '} + in the project root: +

+
+{`# Google — console.cloud.google.com
+TASK_SYNC_PROVIDER_A=google
+TASK_SYNC_PROVIDER_B=microsoft
+TASK_SYNC_GOOGLE_CLIENT_ID=your-client-id
+TASK_SYNC_GOOGLE_CLIENT_SECRET=your-client-secret
+
+# Microsoft — portal.azure.com
+TASK_SYNC_MS_CLIENT_ID=your-client-id
+TASK_SYNC_MS_TENANT_ID=consumers`}
+            
+

+ After saving, restart the dev server. +

+
+
+ )} + + {/* Provider cards */} +
+ handleDisconnect('google')} + /> + handleDisconnect('microsoft')} + /> +
+ + + + {/* Sync section */} + + + Sync + + {bothConnected + ? 'Both providers connected. Ready to sync.' + : 'Connect both providers to enable sync.'} + + + +
+ +
+ + {status?.lastSync && ( +

+ Last synced:{' '} + {new Date(status.lastSync.at).toLocaleString(undefined, { + dateStyle: 'medium', + timeStyle: 'short', + })} + {' · '} + {status.lastSync.mappings} task + {status.lastSync.mappings !== 1 ? 's' : ''} tracked +

+ )} + + {lastReport && } +
+
+ + {/* Footer */} + +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Provider Card */ +/* ------------------------------------------------------------------ */ + +function ProviderCard({ + name, + provider, + configured, + connected, + onDisconnect, +}: { + name: string; + provider: 'google' | 'microsoft'; + configured: boolean; + connected: boolean; + onDisconnect: () => void; +}) { + return ( + + +
+ {name} + {connected ? ( + + + Connected + + ) : configured ? ( + Not connected + ) : ( + + Not configured + + )} +
+
+ + {connected ? ( + + ) : configured ? ( + + ) : ( +

+ Add credentials to{' '} + + .env.local + +

+ )} +
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Sync Report */ +/* ------------------------------------------------------------------ */ + +function SyncReportView({ report }: { report: SyncReport }) { + const total = + (report.counts.create ?? 0) + + (report.counts.update ?? 0) + + (report.counts.delete ?? 0) + + (report.counts.recreate ?? 0); + + return ( +
+
+ Sync Report + {report.durationMs}ms +
+ +
+
+

+ {report.counts.create ?? 0} +

+

Created

+
+
+

+ {report.counts.update ?? 0} +

+

Updated

+
+
+

+ {report.counts.delete ?? 0} +

+

Deleted

+
+
+

+ {report.counts.noop ?? 0} +

+

Unchanged

+
+
+ + {report.errors.length > 0 && ( + + + + {report.errors.map((e, i) => ( +

+ {e.provider} ({e.stage}): {e.error} +

+ ))} +
+
+ )} + + {report.actions.length > 0 && ( +
+ + {total} action{total !== 1 ? 's' : ''} performed + +
    + {report.actions.map((a, i) => ( +
  • + [{a.kind}] {a.target.provider} + {a.title ? ` "${a.title}"` : ''} +
  • + ))} +
+
+ )} +
+ ); +} diff --git a/web/components/ui/alert.tsx b/web/components/ui/alert.tsx new file mode 100644 index 0000000..1421354 --- /dev/null +++ b/web/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/web/components/ui/badge.tsx b/web/components/ui/badge.tsx new file mode 100644 index 0000000..beb56ed --- /dev/null +++ b/web/components/ui/badge.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + link: "text-primary underline-offset-4 [a&]:hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/web/components/ui/button.tsx b/web/components/ui/button.tsx new file mode 100644 index 0000000..b5ea4ab --- /dev/null +++ b/web/components/ui/button.tsx @@ -0,0 +1,64 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot.Root : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/web/components/ui/card.tsx b/web/components/ui/card.tsx new file mode 100644 index 0000000..681ad98 --- /dev/null +++ b/web/components/ui/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/web/components/ui/separator.tsx b/web/components/ui/separator.tsx new file mode 100644 index 0000000..4c24b2a --- /dev/null +++ b/web/components/ui/separator.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import { Separator as SeparatorPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Separator } diff --git a/web/components/ui/sonner.tsx b/web/components/ui/sonner.tsx new file mode 100644 index 0000000..9b20afe --- /dev/null +++ b/web/components/ui/sonner.tsx @@ -0,0 +1,40 @@ +"use client" + +import { + CircleCheckIcon, + InfoIcon, + Loader2Icon, + OctagonXIcon, + TriangleAlertIcon, +} from "lucide-react" +import { useTheme } from "next-themes" +import { Toaster as Sonner, type ToasterProps } from "sonner" + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme() + + return ( + , + info: , + warning: , + error: , + loading: , + }} + style={ + { + "--normal-bg": "var(--popover)", + "--normal-text": "var(--popover-foreground)", + "--normal-border": "var(--border)", + "--border-radius": "var(--radius)", + } as React.CSSProperties + } + {...props} + /> + ) +} + +export { Toaster } diff --git a/web/lib/env.ts b/web/lib/env.ts new file mode 100644 index 0000000..36871c4 --- /dev/null +++ b/web/lib/env.ts @@ -0,0 +1,86 @@ +import { readFileSync, existsSync } from 'node:fs'; +import path from 'node:path'; + +let loaded = false; + +/** Project root directory (parent of web/) */ +export function projectRoot(): string { + return path.resolve(process.cwd(), '..'); +} + +/** Load .env and .env.local from the project root */ +export function loadParentEnv(): void { + if (loaded) return; + loaded = true; + + const root = projectRoot(); + for (const name of ['.env', '.env.local']) { + const filePath = path.join(root, name); + if (!existsSync(filePath)) continue; + + const raw = readFileSync(filePath, 'utf8'); + for (const line of raw.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + + const eq = trimmed.indexOf('='); + if (eq === -1) continue; + const key = trimmed.slice(0, eq).trim(); + let value = trimmed.slice(eq + 1).trim(); + + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + + if (!key) continue; + if (process.env[key] === undefined) process.env[key] = value; + } + } +} + +export interface AppConfig { + googleClientId: string; + googleClientSecret: string; + googleTasklistId?: string; + + msClientId: string; + msClientSecret: string; + msTenantId: string; + msListId?: string; + + stateDir: string; + logLevel: string; +} + +export function getConfig(): AppConfig { + loadParentEnv(); + const root = projectRoot(); + return { + googleClientId: process.env.TASK_SYNC_GOOGLE_CLIENT_ID ?? '', + googleClientSecret: process.env.TASK_SYNC_GOOGLE_CLIENT_SECRET ?? '', + googleTasklistId: process.env.TASK_SYNC_GOOGLE_TASKLIST_ID, + + msClientId: process.env.TASK_SYNC_MS_CLIENT_ID ?? '', + msClientSecret: process.env.TASK_SYNC_MS_CLIENT_SECRET ?? '', + msTenantId: process.env.TASK_SYNC_MS_TENANT_ID ?? 'consumers', + msListId: process.env.TASK_SYNC_MS_LIST_ID, + + stateDir: process.env.TASK_SYNC_STATE_DIR + ? path.resolve(root, process.env.TASK_SYNC_STATE_DIR) + : path.join(root, '.task-sync'), + logLevel: process.env.TASK_SYNC_LOG_LEVEL ?? 'info', + }; +} + +export function isGoogleConfigured(): boolean { + const config = getConfig(); + return !!(config.googleClientId && config.googleClientSecret); +} + +export function isMicrosoftConfigured(): boolean { + const config = getConfig(); + return !!config.msClientId; +} diff --git a/web/lib/tokens.ts b/web/lib/tokens.ts new file mode 100644 index 0000000..1b287a6 --- /dev/null +++ b/web/lib/tokens.ts @@ -0,0 +1,50 @@ +import { readFile, writeFile, mkdir } from 'node:fs/promises'; +import path from 'node:path'; +import { getConfig } from './env'; + +export interface TokenSet { + refreshToken: string; + accessToken?: string; + expiresAt?: number; +} + +export interface StoredTokens { + google?: TokenSet; + microsoft?: TokenSet; +} + +function tokensPath(): string { + return path.join(getConfig().stateDir, 'tokens.json'); +} + +export async function readTokens(): Promise { + try { + const raw = await readFile(tokensPath(), 'utf8'); + return JSON.parse(raw) as StoredTokens; + } catch { + return {}; + } +} + +export async function saveTokens(tokens: StoredTokens): Promise { + const p = tokensPath(); + await mkdir(path.dirname(p), { recursive: true }); + await writeFile(p, JSON.stringify(tokens, null, 2) + '\n', 'utf8'); +} + +export async function saveProviderToken( + provider: 'google' | 'microsoft', + token: TokenSet, +): Promise { + const tokens = await readTokens(); + tokens[provider] = token; + await saveTokens(tokens); +} + +export async function deleteProviderToken( + provider: 'google' | 'microsoft', +): Promise { + const tokens = await readTokens(); + delete tokens[provider]; + await saveTokens(tokens); +} diff --git a/web/lib/utils.ts b/web/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/web/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/web/next.config.ts b/web/next.config.ts new file mode 100644 index 0000000..9e266fe --- /dev/null +++ b/web/next.config.ts @@ -0,0 +1,15 @@ +import type { NextConfig } from 'next'; +import path from 'path'; + +const nextConfig: NextConfig = { + // Silence the "multiple lockfiles" warning + outputFileTracingRoot: path.resolve(process.cwd(), '..'), + + webpack: (config) => { + const alias = config.resolve.alias as Record; + alias['@core'] = path.resolve(process.cwd(), '..', 'dist'); + return config; + }, +}; + +export default nextConfig; diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..f2892c5 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,8008 @@ +{ + "name": "task-sync-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "task-sync-web", + "version": "0.1.0", + "dependencies": { + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.468.0", + "next": "^15", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react": "^19", + "react-dom": "^19", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^22", + "@types/react": "^19", + "@types/react-dom": "^19", + "shadcn": "^3.8.4", + "tailwindcss": "^4", + "tw-animate-css": "^1.4.0", + "typescript": "^5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@antfu/ni": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-25.0.0.tgz", + "integrity": "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.0.0", + "fzf": "^0.5.2", + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "na": "bin/na.mjs", + "nci": "bin/nci.mjs", + "ni": "bin/ni.mjs", + "nlx": "bin/nlx.mjs", + "nr": "bin/nr.mjs", + "nun": "bin/nun.mjs", + "nup": "bin/nup.mjs" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.52.0.tgz", + "integrity": "sha512-CaQcc8JvtzQhUSm9877b6V4Tb7HCotkcyud9X2YwdqtQKwgljkMRwU96fVYKnzN3V0Hj74oP7Es+vZ0mS+Aa1w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^11.1.0", + "dotenv": "^17.2.1", + "eciesjs": "^0.4.10", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "picomatch": "^4.0.2", + "which": "^4.0.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@ecies/ciphers": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.5.tgz", + "integrity": "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "dev": true, + "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/@mswjs/interceptors": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.2.tgz", + "integrity": "sha512-7G0Uf0yK3f2bjElBLGHIQzgRgMESczOMyYVasq1XK8P5HaXtlW4eQhz9MBL+TQILZLaruq+ClGId+hH0w4jvWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@next/env": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz", + "integrity": "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz", + "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz", + "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz", + "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz", + "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.12.tgz", + "integrity": "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.12.tgz", + "integrity": "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz", + "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz", + "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@types/node": { + "version": "22.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.9.tgz", + "integrity": "sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", + "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/validate-npm-package-name": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "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==", + "dev": true, + "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/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/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/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/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/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.2.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", + "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eciesjs": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.17.tgz", + "integrity": "sha512-TOOURki4G7sD1wDCjj7NfLaXZZ49dFOeEb5y39IXpb8p0hRzVvfvzZHOi5JcT+PpyAbi/Y+lxPb8eTag2WYH8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.5", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "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/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "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" + } + }, + "node_modules/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fzf": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fzf/-/fzf-0.5.2.tgz", + "integrity": "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphql": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hono": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.8.tgz", + "integrity": "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.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==", + "dev": true, + "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", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.2.tgz", + "integrity": "sha512-mIcis6w+JiQf3P7t7mg/35GKB4T1FQsBOtMIvuKw4YErj5RjtbhcTd5/I30fmkmGMwvI0WlzSNN+27K0QCMkAw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "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==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "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/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/msw": { + "version": "2.12.9", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.9.tgz", + "integrity": "sha512-NYbi51C6M3dujGmcmuGemu68jy12KqQPoVWGeroKToLGsBgrwG5ErM8WctoIIg49/EV49SEvYM9WSqO4G7kNeQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.41.2", + "@open-draft/deferred-promise": "^2.2.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.0.2", + "graphql": "^16.12.0", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.10.1", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.0", + "type-fest": "^5.2.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", + "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.12", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.12", + "@next/swc-darwin-x64": "15.5.12", + "@next/swc-linux-arm64-gnu": "15.5.12", + "@next/swc-linux-arm64-musl": "15.5.12", + "@next/swc-linux-x64-gnu": "15.5.12", + "@next/swc-linux-x64-musl": "15.5.12", + "@next/swc-win32-arm64-msvc": "15.5.12", + "@next/swc-win32-x64-msvc": "15.5.12", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix-ui": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-accessible-icon": "1.1.7", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-aspect-ratio": "1.1.7", + "@radix-ui/react-avatar": "1.1.10", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-form": "0.1.8", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-one-time-password-field": "0.1.8", + "@radix-ui/react-password-toggle-field": "0.1.3", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-progress": "1.1.7", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-toolbar": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-escape-keydown": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rettime": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", + "integrity": "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==", + "dev": true, + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "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/router/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shadcn": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-3.8.4.tgz", + "integrity": "sha512-pSad/m1+PGzB0aLsRBV0EkyGg9al1nJqYUuucg6d8v8xZspPZ5/ehGNEp5M4b1KQYqdO5/gGPbkhVbgmXqG9Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/ni": "^25.0.0", + "@babel/core": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", + "@dotenvx/dotenvx": "^1.48.4", + "@modelcontextprotocol/sdk": "^1.26.0", + "@types/validate-npm-package-name": "^4.0.2", + "browserslist": "^4.26.2", + "commander": "^14.0.0", + "cosmiconfig": "^9.0.0", + "dedent": "^1.6.0", + "deepmerge": "^4.3.1", + "diff": "^8.0.2", + "execa": "^9.6.0", + "fast-glob": "^3.3.3", + "fs-extra": "^11.3.1", + "fuzzysort": "^3.1.0", + "https-proxy-agent": "^7.0.6", + "kleur": "^4.1.5", + "msw": "^2.10.4", + "node-fetch": "^3.3.2", + "open": "^11.0.0", + "ora": "^8.2.0", + "postcss": "^8.5.6", + "postcss-selector-parser": "^7.1.0", + "prompts": "^2.4.2", + "recast": "^0.23.11", + "stringify-object": "^5.0.0", + "tailwind-merge": "^3.0.1", + "ts-morph": "^26.0.0", + "tsconfig-paths": "^4.2.0", + "validate-npm-package-name": "^7.0.1", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "shadcn": "dist/index.js" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/stringify-object?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tldts": { + "version": "7.0.22", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.22.tgz", + "integrity": "sha512-nqpKFC53CgopKPjT6Wfb6tpIcZXHcI6G37hesvikhx0EmUGPkZrujRyAjgnmp1SHNgpQfKVanZ+KfpANFt2Hxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.22" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.22", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.22.tgz", + "integrity": "sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/ts-morph": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.27.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-fest": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.3.tgz", + "integrity": "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..bc3cdae --- /dev/null +++ b/web/package.json @@ -0,0 +1,33 @@ +{ + "name": "task-sync-web", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.468.0", + "next": "^15", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react": "^19", + "react-dom": "^19", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^22", + "@types/react": "^19", + "@types/react-dom": "^19", + "shadcn": "^3.8.4", + "tailwindcss": "^4", + "tw-animate-css": "^1.4.0", + "typescript": "^5" + } +} diff --git a/web/postcss.config.mjs b/web/postcss.config.mjs new file mode 100644 index 0000000..79bcf13 --- /dev/null +++ b/web/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..1dd29f9 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "ES2022"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "baseUrl": ".", + "paths": { + "@/*": ["./*"], + "@core/*": ["../dist/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From b0f195b6342d11b7d7cfc19d65dbdd7f135545a1 Mon Sep 17 00:00:00 2001 From: deeqdev Date: Sat, 7 Feb 2026 17:39:32 +0300 Subject: [PATCH 2/5] Fix CI: run web E2E tests against Next dev server --- test/web.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/web.test.ts b/test/web.test.ts index 385819a..9539739 100644 --- a/test/web.test.ts +++ b/test/web.test.ts @@ -48,12 +48,12 @@ beforeAll(async () => { // Write to project root .env (the web app reads from parent) await writeFile(path.resolve(import.meta.dirname, '..', '.env.test'), envContent); - // Start the production server - server = spawn('npx', ['next', 'start', '-p', String(PORT)], { + // Start a Next.js dev server (avoids requiring a pre-built .next directory in CI) + server = spawn('npx', ['next', 'dev', '-p', String(PORT)], { cwd: WEB_DIR, env: { ...process.env, - NODE_ENV: 'production', + NODE_ENV: 'development', TASK_SYNC_GOOGLE_CLIENT_ID: 'test-google-id', TASK_SYNC_GOOGLE_CLIENT_SECRET: 'test-google-secret', TASK_SYNC_MS_CLIENT_ID: 'test-ms-id', From a1dc2549cdb068818fdc6ade6d8c9aa70ebfe89d Mon Sep 17 00:00:00 2001 From: deeqdev Date: Sat, 7 Feb 2026 17:41:51 +0300 Subject: [PATCH 3/5] CI: increase web E2E server startup timeout --- test/web.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/web.test.ts b/test/web.test.ts index 9539739..134ee7d 100644 --- a/test/web.test.ts +++ b/test/web.test.ts @@ -18,7 +18,7 @@ let server: ChildProcess; * token storage, and status reporting. */ -async function waitForServer(url: string, timeoutMs = 60_000): Promise { +async function waitForServer(url: string, timeoutMs = 180_000): Promise { const start = Date.now(); while (Date.now() - start < timeoutMs) { try { @@ -63,8 +63,8 @@ beforeAll(async () => { stdio: 'pipe', }); - await waitForServer(`${BASE}/api/status`); -}, 60_000); + await waitForServer(`${BASE}/api/status`, 180_000); +}, 180_000); afterAll(async () => { server?.kill('SIGTERM'); From b59d8c3a9dd2962fe384de51b9017ea8db8a2b24 Mon Sep 17 00:00:00 2001 From: deeqdev Date: Sat, 7 Feb 2026 18:16:01 +0300 Subject: [PATCH 4/5] Add comprehensive README wiki and screenshot Rewrite README as a full public-facing wiki: architecture overview, detailed sync algorithm docs, OAuth setup guides, configuration reference, deployment examples, FAQ, and contributing guidelines. Include web UI screenshot. Co-authored-by: Cursor --- README.md | 848 ++++++++++++++++++++++++++++++----- docs/screenshot.png | Bin 0 -> 69775 bytes test/web.test.ts | 18 +- web/components/dashboard.tsx | 139 +++++- 4 files changed, 877 insertions(+), 128 deletions(-) create mode 100644 docs/screenshot.png diff --git a/README.md b/README.md index 0112ab3..d5d487c 100644 --- a/README.md +++ b/README.md @@ -1,252 +1,866 @@ -# task-sync +

+

Task Sync

+

+ Keep your Google Tasks and Microsoft To Do in sync. +
+ Self-hosted. Bidirectional. Field-level conflict resolution. +

+

+ CI + License + Node.js >= 22 + TypeScript +

+

+ +

+ Task Sync Web UI +

+ +--- + +## Table of Contents + +- [Why Task Sync?](#why-task-sync) +- [Features](#features) +- [Architecture Overview](#architecture-overview) +- [Prerequisites](#prerequisites) +- [Quick Start — Web UI](#quick-start--web-ui) + - [1. Create OAuth Apps](#1-create-oauth-apps) + - [2. Configure Environment](#2-configure-environment) + - [3. Install and Run](#3-install-and-run) +- [Quick Start — CLI](#quick-start--cli) + - [1. Install and Build](#1-install-and-build) + - [2. Obtain Refresh Tokens](#2-obtain-refresh-tokens) + - [3. Configure](#3-configure) + - [4. Run](#4-run) +- [Configuration Reference](#configuration-reference) + - [Required Variables](#required-variables) + - [Optional Variables](#optional-variables) +- [How Syncing Works](#how-syncing-works) + - [Sync Algorithm](#sync-algorithm) + - [Cold-Start Matching](#cold-start-matching) + - [Field-Level Conflict Resolution](#field-level-conflict-resolution) + - [Delete Propagation](#delete-propagation) + - [Sync Modes](#sync-modes) + - [Supported Fields](#supported-fields) + - [Extended Fields (Google Tasks)](#extended-fields-google-tasks) +- [Sync State](#sync-state) +- [Web UI](#web-ui) + - [OAuth Flow](#oauth-flow) + - [Token Storage](#token-storage) + - [Auto-Sync](#auto-sync) + - [API Endpoints](#api-endpoints) +- [CLI Reference](#cli-reference) +- [Project Structure](#project-structure) +- [Development](#development) + - [Running Locally](#running-locally) + - [Testing](#testing) + - [Linting and Type Checking](#linting-and-type-checking) + - [Building](#building) + - [CI/CD](#cicd) +- [Deployment](#deployment) + - [Running as a Service](#running-as-a-service) + - [Reverse Proxy](#reverse-proxy) +- [Security](#security) +- [FAQ](#faq) +- [Contributing](#contributing) +- [License](#license) + +--- + +## Why Task Sync? + +If you use both Google Tasks and Microsoft To Do — maybe Google Tasks on your phone and Microsoft To Do on your work laptop — you know the pain. Tasks live in two silos, and there is no native way to keep them in sync. + +**Task Sync** bridges the gap. It runs on your own machine (or server), connects to both APIs with your credentials, and keeps everything synchronized. No cloud service in the middle. No third-party access to your data. Just a lightweight TypeScript engine that does one thing well. -Sync tasks between **Google Tasks** and **Microsoft To Do**. +## Features -Works as a CLI for power users, or as a self-hosted web UI for everyone else. +| Feature | Description | +|---|---| +| **Bidirectional sync** | Changes in either provider flow to the other automatically | +| **Field-level conflict resolution** | Last-write-wins *per field*, not per task — no data loss | +| **Cold-start matching** | First-time sync deduplicates existing tasks by title + notes | +| **Delete propagation** | Tombstones ensure deleted tasks stay deleted across providers | +| **Dry-run mode** | Preview every change before it happens | +| **Polling / auto-sync** | Set an interval and let it run in the background | +| **Web UI** | OAuth login, one-click sync, auto-sync controls | +| **CLI** | Scriptable, cron-friendly, JSON output for automation | +| **Self-hosted** | Your data never leaves your machine | +| **Zero dependencies at runtime** | Only `commander` and `zod` — no bloat | + +## Architecture Overview -## Features +``` +┌─────────────────────────────────────────────────────────┐ +│ Task Sync │ +│ │ +│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │ +│ │ CLI │ │ Web UI │ │ Sync Engine │ │ +│ │ (cli.ts) │──▶│ (Next.js 15) │──▶│ (engine.ts) │ │ +│ └──────────┘ └──────────────┘ │ │ │ +│ │ Cold-start match│ │ +│ │ Conflict resolve│ │ +│ │ Delete propagate│ │ +│ └────────┬─────────┘ │ +│ │ │ +│ ┌────────────────────────┼──────┐ │ +│ │ │ │ │ +│ ┌─────▼─────┐ ┌──────▼────┐ │ │ +│ │ Google │ │ Microsoft │ │ │ +│ │ Tasks API │ │ Graph API │ │ │ +│ └───────────┘ └───────────┘ │ │ +│ │ │ │ │ +│ └────────────────────────┘ │ │ +│ Providers │ │ +│ │ │ +│ ┌──────────────────────┐ │ │ +│ │ JSON State Store │──────────────┘ │ +│ │ (.task-sync/state) │ │ +│ └──────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` -- **Bidirectional sync** between Google Tasks and Microsoft To Do -- **Field-level conflict resolution** (last-write-wins per field) -- **Cold-start matching** — deduplicates tasks on first sync by title + notes -- **Delete propagation** — tombstones prevent resurrecting deleted tasks -- **Dry-run mode** — preview changes before applying -- **Polling mode** — auto-sync on an interval -- **Web UI** — connect accounts with OAuth, sync with one click -- **Self-hosted** — your data stays on your machine +The engine is provider-agnostic. Google and Microsoft are implemented as `TaskProvider` interfaces, making it straightforward to add new providers in the future. -## Requirements +## Prerequisites -- Node.js **>= 22** +- **Node.js >= 22** (LTS recommended) +- A **Google Cloud** project with the Tasks API enabled +- A **Microsoft Azure** app registration with To Do permissions +- npm (comes with Node.js) ## Quick Start — Web UI -The web UI lets you connect your Google and Microsoft accounts via OAuth -and sync tasks with one click. No manual token management needed. +The web UI lets you connect your Google and Microsoft accounts via OAuth and sync tasks with one click. No manual token management required. -### 1. Set up OAuth apps +### 1. Create OAuth Apps -**Google Tasks:** +
+Google Tasks — step-by-step -1. Go to [Google Cloud Console](https://console.cloud.google.com/) -2. Create a project (or select existing) -3. Enable the **Google Tasks API** +1. Go to the [Google Cloud Console](https://console.cloud.google.com/) +2. Create a new project (or select an existing one) +3. Navigate to **APIs & Services** and enable the **Google Tasks API** 4. Go to **APIs & Services → Credentials → Create Credentials → OAuth client ID** 5. Application type: **Web application** -6. Add authorized redirect URI: `http://localhost:3000/api/auth/google/callback` +6. Under **Authorized redirect URIs**, add: + ``` + http://localhost:3000/api/auth/google/callback + ``` 7. Copy the **Client ID** and **Client Secret** -**Microsoft To Do:** +> **Note:** If you publish the app to production with a custom domain, update the redirect URI accordingly. + +
+ +
+Microsoft To Do — step-by-step 1. Go to [Azure Portal → App registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) -2. **New registration** -3. Supported account types: **Personal Microsoft accounts only** (or multi-tenant) -4. Redirect URI (Web): `http://localhost:3000/api/auth/microsoft/callback` -5. Go to **API permissions** → Add: `Tasks.ReadWrite`, `User.Read`, `offline_access` -6. Copy the **Application (client) ID** +2. Click **New registration** +3. Name: `Task Sync` (or anything you like) +4. Supported account types: **Personal Microsoft accounts only** (or multi-tenant if needed) +5. Redirect URI → Platform: **Web** → URI: + ``` + http://localhost:3000/api/auth/microsoft/callback + ``` +6. After creation, go to **API permissions** → **Add a permission** → **Microsoft Graph** → **Delegated permissions**: + - `Tasks.ReadWrite` + - `User.Read` + - `offline_access` +7. Go to **Certificates & secrets** → **New client secret** → copy the **Value** +8. Copy the **Application (client) ID** from the Overview page + +> **Note:** For personal Microsoft accounts, use `consumers` as the tenant ID. For work/school accounts, use your Azure AD tenant ID. + +
-### 2. Configure +### 2. Configure Environment -Create `.env.local` in the project root: +Create a `.env.local` file in the project root: ```bash TASK_SYNC_PROVIDER_A=google TASK_SYNC_PROVIDER_B=microsoft +# Google Tasks TASK_SYNC_GOOGLE_CLIENT_ID=your-google-client-id TASK_SYNC_GOOGLE_CLIENT_SECRET=your-google-client-secret +# Microsoft To Do TASK_SYNC_MS_CLIENT_ID=your-microsoft-client-id +TASK_SYNC_MS_CLIENT_SECRET=your-microsoft-client-secret TASK_SYNC_MS_TENANT_ID=consumers ``` -### 3. Install and run +> A `.env.local.example` template is included in the repo for reference. + +### 3. Install and Run ```bash +git clone https://github.com/salaamdev/task-sync.git +cd task-sync npm install npm run web:install npm run web:dev ``` -Open [http://localhost:3000](http://localhost:3000). Click **Connect** for each -provider, approve the OAuth consent, then hit **Sync Now**. +Open [http://localhost:3000](http://localhost:3000). Click **Connect** for each provider, approve the OAuth consent screen, then hit **Sync Now**. -### Production +#### Production Build ```bash npm run web:build npm run web:start ``` +The production server runs on port 3000 by default. + +--- + ## Quick Start — CLI -For headless environments, scripts, or cron jobs. +For headless environments, scripts, cron jobs, or if you prefer the terminal. -### 1. Install +### 1. Install and Build ```bash +git clone https://github.com/salaamdev/task-sync.git +cd task-sync npm install npm run build ``` -### 2. Get refresh tokens +### 2. Obtain Refresh Tokens -You need refresh tokens for each provider. Helper scripts are included: +Helper scripts are included to walk you through the OAuth flow in your browser: ```bash -# Google -export TASK_SYNC_GOOGLE_CLIENT_ID=... -export TASK_SYNC_GOOGLE_CLIENT_SECRET=... +# Google Tasks +export TASK_SYNC_GOOGLE_CLIENT_ID=your-client-id +export TASK_SYNC_GOOGLE_CLIENT_SECRET=your-client-secret npm run oauth:google +``` -# Microsoft -export TASK_SYNC_MS_CLIENT_ID=... +```bash +# Microsoft To Do +export TASK_SYNC_MS_CLIENT_ID=your-client-id export TASK_SYNC_MS_TENANT_ID=consumers npm run oauth:microsoft ``` -Each script opens a browser for consent, then prints the refresh token. +Each script starts a local server, opens your browser for consent, and prints the refresh token when done. ### 3. Configure -Add all tokens to `.env.local`: +Add everything to `.env.local`: ```bash TASK_SYNC_PROVIDER_A=google TASK_SYNC_PROVIDER_B=microsoft -TASK_SYNC_GOOGLE_CLIENT_ID=... -TASK_SYNC_GOOGLE_CLIENT_SECRET=... -TASK_SYNC_GOOGLE_REFRESH_TOKEN=... +TASK_SYNC_GOOGLE_CLIENT_ID=your-client-id +TASK_SYNC_GOOGLE_CLIENT_SECRET=your-client-secret +TASK_SYNC_GOOGLE_REFRESH_TOKEN=your-refresh-token -TASK_SYNC_MS_CLIENT_ID=... +TASK_SYNC_MS_CLIENT_ID=your-client-id TASK_SYNC_MS_TENANT_ID=consumers -TASK_SYNC_MS_REFRESH_TOKEN=... +TASK_SYNC_MS_REFRESH_TOKEN=your-refresh-token ``` ### 4. Run ```bash -# Check config +# Validate your configuration node dist/cli.js doctor +# Preview changes without applying them +node dist/cli.js sync --dry-run + # Sync once node dist/cli.js sync -# Dry-run (preview changes) -node dist/cli.js sync --dry-run - # Auto-sync every 5 minutes node dist/cli.js sync --poll 5 -# JSON output (for scripts) +# JSON output (for scripting / piping) node dist/cli.js sync --format json ``` -## Configuration +--- + +## Configuration Reference -All configuration is via environment variables. Create a `.env.local` file in the -project root. +All configuration is done through environment variables. Place them in a `.env.local` file in the project root (git-ignored by default). -### Required +### Required Variables | Variable | Description | |---|---| -| `TASK_SYNC_PROVIDER_A` | First provider (`google` or `microsoft`) | -| `TASK_SYNC_PROVIDER_B` | Second provider (`google` or `microsoft`) | +| `TASK_SYNC_PROVIDER_A` | First provider: `google` or `microsoft` | +| `TASK_SYNC_PROVIDER_B` | Second provider: `google` or `microsoft` | | `TASK_SYNC_GOOGLE_CLIENT_ID` | Google OAuth client ID | | `TASK_SYNC_GOOGLE_CLIENT_SECRET` | Google OAuth client secret | -| `TASK_SYNC_MS_CLIENT_ID` | Microsoft app (client) ID | +| `TASK_SYNC_MS_CLIENT_ID` | Microsoft application (client) ID | -### Optional +### Optional Variables | Variable | Default | Description | |---|---|---| -| `TASK_SYNC_MS_TENANT_ID` | `consumers` | Azure tenant ID | -| `TASK_SYNC_GOOGLE_REFRESH_TOKEN` | — | CLI only: Google refresh token | -| `TASK_SYNC_MS_REFRESH_TOKEN` | — | CLI only: Microsoft refresh token | -| `TASK_SYNC_GOOGLE_TASKLIST_ID` | `@default` | Google task list to sync | -| `TASK_SYNC_MS_LIST_ID` | First list | Microsoft To Do list to sync | -| `TASK_SYNC_STATE_DIR` | `.task-sync` | Directory for sync state | -| `TASK_SYNC_LOG_LEVEL` | `info` | Log level: `silent\|error\|warn\|info\|debug` | -| `TASK_SYNC_POLL_INTERVAL_MINUTES` | — | Auto-sync interval (CLI only) | -| `TASK_SYNC_MODE` | `bidirectional` | Sync mode: `bidirectional\|a-to-b-only\|mirror` | -| `TASK_SYNC_TOMBSTONE_TTL_DAYS` | `30` | How long to keep delete tombstones | +| `TASK_SYNC_MS_CLIENT_SECRET` | — | Microsoft client secret (required for web UI) | +| `TASK_SYNC_MS_TENANT_ID` | `consumers` | Azure AD tenant ID | +| `TASK_SYNC_GOOGLE_REFRESH_TOKEN` | — | Google refresh token (CLI only) | +| `TASK_SYNC_MS_REFRESH_TOKEN` | — | Microsoft refresh token (CLI only) | +| `TASK_SYNC_GOOGLE_TASKLIST_ID` | `@default` | Google Tasks list ID to sync | +| `TASK_SYNC_MS_LIST_ID` | *(first list)* | Microsoft To Do list ID to sync | +| `TASK_SYNC_STATE_DIR` | `.task-sync` | Directory for sync state files | +| `TASK_SYNC_LOG_LEVEL` | `info` | Log verbosity: `silent`, `error`, `warn`, `info`, `debug` | +| `TASK_SYNC_POLL_INTERVAL_MINUTES` | — | Auto-sync interval in minutes (CLI only) | +| `TASK_SYNC_MODE` | `bidirectional` | Sync mode (see [Sync Modes](#sync-modes)) | +| `TASK_SYNC_TOMBSTONE_TTL_DAYS` | `30` | Days to retain delete tombstones | +| `TASK_SYNC_HTTP_RPS` | — | Rate limit (requests per second) for API calls | + +--- + +## How Syncing Works -## How It Works +### Sync Algorithm -### Sync State +Each sync run follows these steps: -`task-sync` stores local state in `.task-sync/state.json`: +1. **Lock** — acquire a file-based lock to prevent concurrent syncs +2. **Load state** — read mappings, tombstones, and `lastSyncAt` from `.task-sync/state.json` +3. **Prune tombstones** — remove expired tombstones (older than `TASK_SYNC_TOMBSTONE_TTL_DAYS`) +4. **Fetch tasks** — incremental fetch using `lastSyncAt` as a watermark (full fetch on first run) +5. **Cold-start match** — on the first sync, match existing tasks across providers by title + notes +6. **Detect deletes** — find tasks that were deleted and create tombstones +7. **Detect orphans** — handle tasks missing from one or both providers +8. **Reconcile fields** — for each mapped task, diff every field against the last known canonical snapshot +9. **Resolve conflicts** — if multiple providers changed the same field, apply [conflict resolution](#field-level-conflict-resolution) +10. **Fan out** — push the resolved canonical state to all providers +11. **Save state** — persist updated mappings, tombstones, and new `lastSyncAt` -- **`lastSyncAt`** — watermark timestamp for incremental sync -- **`mappings`** — links canonical task IDs to provider-specific IDs -- **`tombstones`** — prevents resurrecting deleted tasks +### Cold-Start Matching -Delete `.task-sync/` to reset all sync state. +When you first run Task Sync, you probably already have tasks in both Google Tasks and Microsoft To Do. Instead of duplicating everything, the engine: -### Web UI Token Storage +1. Groups all tasks by a normalized key of `title + notes` +2. Matches tasks that have the same key across providers +3. Creates internal mappings for matched tasks +4. Only creates new tasks for genuinely unmatched items -The web UI stores OAuth refresh tokens in `.task-sync/tokens.json`. These -tokens never leave your machine. The web server uses them to authenticate -with Google and Microsoft APIs on your behalf during sync. +This means your first sync won't flood either provider with duplicates. -### Sync Algorithm +### Field-Level Conflict Resolution + +Unlike simpler sync tools that operate at the task level (entire task wins or loses), Task Sync resolves conflicts **per field**: + +- Each field (title, notes, status, due date, etc.) is compared individually against the last canonical snapshot +- If only one provider changed a field → that change wins +- If multiple providers changed the *same* field → **last-write-wins** based on `updatedAt` timestamp +- Conflicts are logged to `.task-sync/conflicts.log` for transparency + +**Example:** You update a task's title in Google Tasks and its due date in Microsoft To Do. Both changes are preserved — no data loss. + +### Delete Propagation + +Deleting a task in one provider should delete it in the other. Task Sync handles this with **tombstones**: + +1. When a previously-synced task disappears from a provider, the engine creates a tombstone record +2. The delete is propagated to all other providers +3. Tombstones are retained for `TASK_SYNC_TOMBSTONE_TTL_DAYS` (default: 30) to prevent the task from being "resurrected" if it reappears in a cached API response +4. Expired tombstones are pruned automatically + +### Sync Modes + +| Mode | Behavior | +|---|---| +| `bidirectional` | Changes flow both ways (default) | +| `a-to-b-only` | Changes from Provider A are pushed to Provider B, but not vice versa | +| `mirror` | Provider A is the source of truth; Provider B mirrors it exactly | + +Set via `TASK_SYNC_MODE` in your `.env.local`. + +### Supported Fields + +| Field | Google Tasks | Microsoft To Do | Notes | +|---|:---:|:---:|---| +| `title` | Yes | Yes | | +| `notes` | Yes | Yes | | +| `status` | Yes | Yes | `active` / `completed` | +| `dueAt` | Yes | Yes | Date only (time via extended fields) | +| `dueTime` | Extended | Yes | Stored in Google notes metadata | +| `reminder` | — | Yes | Preserved via round-trip metadata | +| `recurrence` | — | Yes | Preserved via round-trip metadata | +| `categories` | — | Yes | Preserved via round-trip metadata | +| `importance` | — | Yes | `low` / `normal` / `high` | +| `steps` | — | Yes | Checklist items (read-only sync) | +| `startAt` | — | Yes | Preserved via round-trip metadata | + +### Extended Fields (Google Tasks) + +Google Tasks has a limited API (title, notes, status, due date). To preserve Microsoft To Do's richer fields, Task Sync stores them in a metadata block appended to the Google Tasks notes field: + +``` +Your actual notes content here... + +[task-sync] +dueTime=14:30 +reminder=2026-02-07T14:00:00Z +importance=high +[/task-sync] +``` + +This metadata block is: +- Invisible to casual use (at the bottom of notes) +- Parsed and stripped during sync +- Enables lossless round-tripping of fields that Google Tasks doesn't natively support + +--- + +## Sync State -1. **Fetch** tasks from all providers (incremental via `lastSyncAt`) -2. **Cold-start match** — on first run, match tasks by title+notes to avoid duplicates -3. **Tombstone check** — skip tasks that were intentionally deleted -4. **Field-level diff** — compare each field (title, notes, status, due date) against the last canonical snapshot -5. **Conflict resolution** — if multiple providers changed the same field, last-write-wins -6. **Fan out** — apply the resolved canonical state to all providers +Task Sync stores all local state in the `.task-sync/` directory (configurable via `TASK_SYNC_STATE_DIR`): + +``` +.task-sync/ +├── state.json # Mappings, tombstones, lastSyncAt watermark +├── tokens.json # OAuth refresh tokens (web UI only) +├── conflicts.log # Conflict resolution audit log +└── state.lock # File-based lock (prevents concurrent syncs) +``` + +### `state.json` Structure + +```jsonc +{ + "version": 1, + "lastSyncAt": "2026-02-07T18:03:00.000Z", + "mappings": [ + { + "canonicalId": "uuid-v4", + "byProvider": { + "google": "google-task-id", + "microsoft": "microsoft-task-id" + }, + "canonical": { + "title": "Buy groceries", + "notes": "Milk, eggs, bread", + "status": "active", + "dueAt": "2026-02-08", + "updatedAt": "2026-02-07T18:03:00.000Z" + }, + "updatedAt": "2026-02-07T18:03:00.000Z" + } + ], + "tombstones": [ + { + "provider": "google", + "id": "deleted-task-id", + "deletedAt": "2026-02-01T12:00:00.000Z" + } + ] +} +``` + +**Resetting sync state:** Delete the `.task-sync/` directory to start fresh. The next sync will perform a full cold-start match. + +--- + +## Web UI + +The web UI is built with **Next.js 15** (App Router), **React 19**, **Tailwind CSS 4**, and **shadcn/ui** components. + +### OAuth Flow + +1. Click **Connect** for a provider +2. You're redirected to Google/Microsoft's OAuth consent screen +3. After approval, you're redirected back to the app +4. The refresh token is stored locally in `.task-sync/tokens.json` + +### Token Storage + +- Tokens are stored in `.task-sync/tokens.json` on disk +- They **never leave your machine** — the web server uses them server-side only +- Treat this file like a password (it's git-ignored by default) + +### Auto-Sync + +The web UI supports automatic polling. Choose an interval (1m, 2m, 5m, 10m, 30m) and the app will sync in the background. The countdown timer shows when the next sync will occur. + +### API Endpoints + +| Method | Path | Description | +|---|---|---| +| `GET` | `/api/auth/google` | Initiates Google OAuth flow | +| `GET` | `/api/auth/google/callback` | Google OAuth callback | +| `GET` | `/api/auth/microsoft` | Initiates Microsoft OAuth flow | +| `GET` | `/api/auth/microsoft/callback` | Microsoft OAuth callback | +| `DELETE` | `/api/disconnect/[provider]` | Removes stored tokens for a provider | +| `GET` | `/api/status` | Returns connection status and last sync info | +| `POST` | `/api/sync` | Triggers a sync and returns the report | + +--- + +## CLI Reference + +``` +Usage: task-sync [command] [options] + +Commands: + doctor Validate configuration and connectivity + sync Run a sync cycle + +Sync options: + --dry-run Preview changes without applying them + --poll Auto-sync on an interval + --format Output format: text (default) or json +``` + +**Examples:** + +```bash +# Check that everything is configured correctly +task-sync doctor + +# One-shot sync +task-sync sync + +# Preview what would change +task-sync sync --dry-run + +# Continuous sync every 10 minutes +task-sync sync --poll 10 + +# Machine-readable output +task-sync sync --format json +``` + +**Using via npm scripts (development):** + +```bash +npm run dev -- doctor +npm run dev -- sync --dry-run +npm run dev -- sync --poll 5 +``` + +--- ## Project Structure ``` task-sync/ -├── src/ # Core engine + CLI -│ ├── cli.ts # CLI entry point -│ ├── sync/engine.ts # Sync algorithm -│ ├── providers/ # Google, Microsoft, Mock providers -│ ├── store/ # State persistence (JSON) -│ ├── model.ts # Task data model -│ └── ... -├── web/ # Next.js web UI -│ ├── app/ # Pages + API routes -│ │ ├── page.tsx # Dashboard -│ │ └── api/ # OAuth + sync endpoints -│ ├── components/ # React components (shadcn/ui) -│ └── lib/ # Env loading, token storage -├── test/ # Vitest tests -├── scripts/ # OAuth helper scripts -└── .env.local # Your credentials (git-ignored) +├── .github/ +│ └── workflows/ +│ └── ci.yml # GitHub Actions CI pipeline +├── scripts/ +│ ├── dev/ # Development utilities +│ │ ├── e2e.ts # End-to-end test helper +│ │ ├── mutate.ts # Task mutation helper +│ │ └── purge_all_lists.ts # Purge all tasks (dev only) +│ ├── google_oauth.ts # CLI OAuth flow for Google +│ └── microsoft_oauth.ts # CLI OAuth flow for Microsoft +├── src/ # Core engine + CLI +│ ├── cli.ts # CLI entry point (Commander.js) +│ ├── config.ts # Env config validation (Zod) +│ ├── env.ts # .env file loader +│ ├── extended-fields.ts # Google Tasks metadata block handling +│ ├── http.ts # HTTP client with retries & rate limiting +│ ├── log.ts # Structured logging +│ ├── model.ts # Task data model & TypeScript types +│ ├── providers/ +│ │ ├── provider.ts # TaskProvider interface +│ │ ├── google.ts # Google Tasks API implementation +│ │ ├── microsoft.ts # Microsoft Graph API implementation +│ │ └── mock.ts # In-memory mock (for testing) +│ ├── store/ +│ │ ├── jsonStore.ts # JSON file-based state persistence +│ │ └── lock.ts # File-based mutual exclusion lock +│ └── sync/ +│ └── engine.ts # Core sync algorithm +├── test/ # Test suite (Vitest) +│ ├── engine.test.ts # Sync engine unit tests +│ ├── engine.hardening.test.ts# Edge case & hardening tests +│ ├── googleProvider.test.ts # Google provider tests +│ ├── microsoftProvider.test.ts# Microsoft provider tests +│ ├── jsonStore.test.ts # State store tests +│ └── web.test.ts # Web UI / API route tests +├── web/ # Next.js web UI +│ ├── app/ +│ │ ├── api/ # API routes (OAuth, sync, status) +│ │ ├── layout.tsx # Root layout +│ │ ├── page.tsx # Dashboard page +│ │ └── globals.css # Global styles +│ ├── components/ +│ │ ├── dashboard.tsx # Main dashboard component +│ │ └── ui/ # shadcn/ui primitives +│ ├── lib/ +│ │ ├── env.ts # Web environment config +│ │ ├── tokens.ts # Token storage (read/write) +│ │ └── utils.ts # Utility functions +│ ├── next.config.ts +│ ├── package.json +│ └── tsconfig.json +├── .env.local.example # Environment variable template +├── .gitignore +├── eslint.config.js +├── LICENSE # Apache License 2.0 +├── package.json +├── tsconfig.json +└── tsup.config.ts # Build configuration ``` +--- + ## Development +### Running Locally + ```bash -# Run CLI in dev mode +# Install all dependencies +npm install +npm run web:install + +# Run CLI in development mode (uses tsx, no build step) npm run dev -- doctor npm run dev -- sync --dry-run -# Run web in dev mode (builds core first) +# Run web UI in development mode (builds core engine first) npm run web:dev +``` + +### Testing + +Tests use [Vitest](https://vitest.dev/) with an in-memory mock provider — no real API credentials needed for the test suite. -# Tests +```bash +# Run all tests once npm test -# Lint & typecheck +# Watch mode (re-runs on file changes) +npm run test:watch +``` + +### Linting and Type Checking + +```bash +# ESLint npm run lint + +# TypeScript type checking (no emit) npm run typecheck ``` -## Security Notes +### Building + +```bash +# Build the core engine (CLI) with tsup → dist/ +npm run build + +# Build core + Next.js web UI for production +npm run web:build +``` + +### CI/CD + +The project uses **GitHub Actions** for continuous integration. Every push and pull request triggers: + +1. `npm ci` — clean install +2. `npm run lint` — ESLint checks +3. `npm run typecheck` — TypeScript validation +4. `npm test` — full test suite +5. `npm run build` — production build verification + +--- + +## Deployment + +### Running as a Service + +**Using systemd (Linux):** + +```ini +[Unit] +Description=Task Sync Web UI +After=network.target + +[Service] +Type=simple +WorkingDirectory=/path/to/task-sync +ExecStart=/usr/bin/node dist/cli.js sync --poll 5 +Restart=on-failure +EnvironmentFile=/path/to/task-sync/.env.local + +[Install] +WantedBy=multi-user.target +``` + +**Using cron (CLI mode):** -- **Self-hosted only** — this project is designed to run on your own machine or server. -- **No telemetry** — no data is sent anywhere except Google and Microsoft APIs. -- **Tokens on disk** — refresh tokens are stored in `.task-sync/tokens.json`. Treat this file like a password. -- **No auth on the web UI** — if you expose the web UI to the internet, put it behind a reverse proxy with authentication (e.g., Caddy, nginx + basic auth, Cloudflare Tunnel). +```bash +# Sync every 15 minutes +*/15 * * * * cd /path/to/task-sync && node dist/cli.js sync >> /var/log/task-sync.log 2>&1 +``` + +**Using PM2:** + +```bash +pm2 start "npm run web:start" --name task-sync +pm2 save +pm2 startup +``` + +### Reverse Proxy + +If you expose the web UI beyond localhost, **always put it behind authentication**. The web UI has no built-in auth. + +
+Caddy (recommended — automatic HTTPS) + +``` +tasks.example.com { + basicauth { + admin $2a$14$... # caddy hash-password + } + reverse_proxy localhost:3000 +} +``` +
+ +
+nginx + +```nginx +server { + listen 443 ssl; + server_name tasks.example.com; + + auth_basic "Task Sync"; + auth_basic_user_file /etc/nginx/.htpasswd; + + location / { + proxy_pass http://localhost:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` +
+ +--- + +## Security + +| Principle | Detail | +|---|---| +| **Self-hosted only** | Designed to run on your own machine or server — no SaaS component | +| **No telemetry** | Zero tracking. The only outbound requests go to Google and Microsoft APIs | +| **Tokens on disk** | Refresh tokens are stored in `.task-sync/tokens.json` — treat it like a password file | +| **No built-in auth** | The web UI has no login screen. If exposed to the internet, use a reverse proxy with authentication | +| **File-based locking** | Prevents concurrent sync runs from corrupting state | +| **Minimal dependencies** | Only 2 runtime dependencies (`commander`, `zod`) — smaller attack surface | + +### Best Practices + +- Keep `.env.local` and `.task-sync/` out of version control (both are in `.gitignore`) +- Use a firewall or VPN if running on a remote server +- Regularly rotate OAuth credentials if you suspect compromise +- Review `.task-sync/conflicts.log` periodically for unexpected conflict patterns + +--- + +## FAQ + +
+What happens if I delete .task-sync/? + +The next sync will behave like a first-time sync: cold-start matching by title + notes, no tombstones. Existing tasks won't be duplicated if they have the same title and notes in both providers. +
+ +
+Can I sync multiple task lists? + +Currently, Task Sync syncs one list per provider. Set `TASK_SYNC_GOOGLE_TASKLIST_ID` and `TASK_SYNC_MS_LIST_ID` to choose which lists. To sync multiple lists, run separate instances with different state directories (`TASK_SYNC_STATE_DIR`). +
+ +
+What if both providers change the same task at the same time? + +Task Sync uses field-level conflict resolution with a last-write-wins policy. Each field is compared independently, so if you change the title in Google and the due date in Microsoft, both changes are preserved. If both change the same field, the most recent edit wins. All conflicts are logged to `.task-sync/conflicts.log`. +
+ +
+Does it support recurring tasks? + +Recurrence rules from Microsoft To Do are preserved and round-tripped, but they are stored as metadata in Google Tasks (which doesn't natively support recurrence). The recurrence will function in Microsoft To Do and be preserved if the task is edited in Google Tasks. +
+ +
+How do I sync with a work/school Microsoft account? + +Set `TASK_SYNC_MS_TENANT_ID` to your Azure AD tenant ID instead of `consumers`. Make sure your Azure app registration supports the appropriate account types. +
+ +
+Can I run the CLI and web UI at the same time? + +Not recommended. Both use the same state directory and file lock, so only one sync process should run at a time. The file lock prevents concurrent corruption, but running both may cause unexpected behavior. +
+ +
+Is there a Docker image? + +Not yet — contributions welcome! The app is a standard Node.js project, so containerizing it is straightforward. +
+ +
+My Google refresh token expired. What do I do? + +Google refresh tokens can expire if the app is in "Testing" mode in the Cloud Console and hasn't been used for 7 days. Either re-run `npm run oauth:google` (CLI) or click **Disconnect** then **Connect** again (web UI). To avoid this, publish your OAuth consent screen (even as internal-only). +
+ +--- + +## Contributing + +Contributions are welcome! Here's how to get started: + +1. **Fork** the repository +2. **Create a feature branch:** `git checkout -b feat/my-feature` +3. **Install dependencies:** `npm install && npm run web:install` +4. **Make your changes** and add tests +5. **Run the checks:** + ```bash + npm run lint + npm run typecheck + npm test + ``` +6. **Commit** with a clear message +7. **Open a Pull Request** against `main` + +### Ideas for Contribution + +- Docker / docker-compose setup +- Additional providers (Todoist, Apple Reminders, etc.) +- Multi-list sync support +- Webhook-based sync (instead of polling) +- Better conflict resolution UI in the web dashboard +- Task list selection in the web UI + +--- ## License -MIT (see [LICENSE](LICENSE)) +Licensed under the [Apache License 2.0](LICENSE). + +``` +Copyright 2025 salaamdev + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +``` diff --git a/docs/screenshot.png b/docs/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..36ea0c827c729b05ae045b792fbe3493189afb74 GIT binary patch literal 69775 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 QL%y`MxPn-@h+)vb0N0`XYybcN literal 0 HcmV?d00001 diff --git a/test/web.test.ts b/test/web.test.ts index 134ee7d..bb1da6b 100644 --- a/test/web.test.ts +++ b/test/web.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; -import { ChildProcess, spawn } from 'node:child_process'; +import { ChildProcess, spawn, spawnSync } from 'node:child_process'; import path from 'node:path'; -import { writeFile, mkdir, rm, readFile } from 'node:fs/promises'; +import { writeFile, mkdir, rm, readFile, stat } from 'node:fs/promises'; const WEB_DIR = path.resolve(import.meta.dirname, '..', 'web'); const STATE_DIR = path.resolve(import.meta.dirname, '..', '.task-sync-test-web'); @@ -48,6 +48,17 @@ beforeAll(async () => { // Write to project root .env (the web app reads from parent) await writeFile(path.resolve(import.meta.dirname, '..', '.env.test'), envContent); + // Ensure web dependencies exist in CI (root npm ci does not install ./web) + const webNodeModules = path.join(WEB_DIR, 'node_modules'); + const hasWebDeps = await stat(webNodeModules).then(() => true).catch(() => false); + + // In GitHub Actions, the workflow does `npm ci` at repo root only. + // These E2E tests need ./web dependencies, so we install them here. + if (process.env.CI && !hasWebDeps) { + const res = spawnSync('npm', ['ci'], { cwd: WEB_DIR, stdio: 'inherit' }); + if (res.status !== 0) throw new Error(`web npm ci failed: ${res.status}`); + } + // Start a Next.js dev server (avoids requiring a pre-built .next directory in CI) server = spawn('npx', ['next', 'dev', '-p', String(PORT)], { cwd: WEB_DIR, @@ -63,6 +74,9 @@ beforeAll(async () => { stdio: 'pipe', }); + server.stdout?.on('data', (d) => process.stdout.write(String(d))); + server.stderr?.on('data', (d) => process.stderr.write(String(d))); + await waitForServer(`${BASE}/api/status`, 180_000); }, 180_000); diff --git a/web/components/dashboard.tsx b/web/components/dashboard.tsx index 90b4942..ab7afda 100644 --- a/web/components/dashboard.tsx +++ b/web/components/dashboard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; import { Card, CardContent, @@ -21,6 +21,8 @@ import { AlertCircle, ArrowRightLeft, Settings, + Timer, + Pause, } from 'lucide-react'; /* ------------------------------------------------------------------ */ @@ -52,6 +54,15 @@ interface SyncReport { durationMs: number; } +const POLL_OPTIONS = [ + { label: 'Off', value: 0 }, + { label: '1m', value: 1 }, + { label: '2m', value: 2 }, + { label: '5m', value: 5 }, + { label: '10m', value: 10 }, + { label: '30m', value: 30 }, +] as const; + /* ------------------------------------------------------------------ */ /* Dashboard */ /* ------------------------------------------------------------------ */ @@ -63,6 +74,14 @@ export function Dashboard() { const [lastReport, setLastReport] = useState(null); const [error, setError] = useState(null); + // Polling state + const [pollMinutes, setPollMinutes] = useState(() => { + if (typeof window === 'undefined') return 0; + return Number(localStorage.getItem('task-sync-poll') ?? '0'); + }); + const [secondsLeft, setSecondsLeft] = useState(0); + const syncingRef = useRef(false); + const fetchStatus = useCallback(async () => { try { const res = await fetch('/api/status'); @@ -98,25 +117,76 @@ export function Dashboard() { /* Sync ----------------------------------------------------------- */ - const handleSync = async () => { + const doSync = useCallback(async (silent = false) => { + if (syncingRef.current) return; + syncingRef.current = true; setSyncing(true); - setLastReport(null); + if (!silent) setLastReport(null); try { const res = await fetch('/api/sync', { method: 'POST' }); const data = await res.json(); if (!res.ok) { - toast.error(data.error || 'Sync failed'); + if (!silent) toast.error(data.error || 'Sync failed'); } else { - setLastReport(data as SyncReport); - toast.success(`Synced in ${(data as SyncReport).durationMs}ms`); + const report = data as SyncReport; + setLastReport(report); + const changes = + (report.counts.create ?? 0) + + (report.counts.update ?? 0) + + (report.counts.delete ?? 0); + if (silent && changes > 0) { + toast.success(`Auto-sync: ${changes} change${changes !== 1 ? 's' : ''}`); + } else if (!silent) { + toast.success(`Synced in ${report.durationMs}ms`); + } fetchStatus(); } } catch { - toast.error('Sync request failed'); + if (!silent) toast.error('Sync request failed'); } finally { setSyncing(false); + syncingRef.current = false; } - }; + }, [fetchStatus]); + + const handleSync = () => doSync(false); + + /* Polling --------------------------------------------------------- */ + + const changePollInterval = useCallback( + (minutes: number) => { + setPollMinutes(minutes); + localStorage.setItem('task-sync-poll', String(minutes)); + if (minutes > 0) { + setSecondsLeft(minutes * 60); + toast.success(`Auto-sync every ${minutes} minute${minutes > 1 ? 's' : ''}`); + } else { + setSecondsLeft(0); + toast.success('Auto-sync disabled'); + } + }, + [], + ); + + // Countdown timer + auto-sync trigger + useEffect(() => { + if (pollMinutes <= 0) return; + + setSecondsLeft(pollMinutes * 60); + + const interval = setInterval(() => { + setSecondsLeft((prev) => { + if (prev <= 1) { + // Trigger sync + doSync(true); + return pollMinutes * 60; // Reset countdown + } + return prev - 1; + }); + }, 1000); + + return () => clearInterval(interval); + }, [pollMinutes, doSync]); /* Disconnect ------------------------------------------------------ */ @@ -234,7 +304,8 @@ TASK_SYNC_MS_TENANT_ID=consumers`} : 'Connect both providers to enable sync.'} - + + {/* Sync Now button */}
+ {/* Auto-sync polling selector */} + {bothConnected && ( +
+
+ + Auto-sync +
+
+ {POLL_OPTIONS.map((opt) => ( + + ))} +
+ {pollMinutes > 0 && ( +
+ {syncing ? ( + <> + + Syncing… + + ) : ( + <> + + Next sync in {formatCountdown(secondsLeft)} + + )} +
+ )} +
+ )} + + {/* Last sync info */} {status?.lastSync && (

Last synced:{' '} @@ -290,6 +400,17 @@ TASK_SYNC_MS_TENANT_ID=consumers`} ); } +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +function formatCountdown(totalSeconds: number): string { + const m = Math.floor(totalSeconds / 60); + const s = totalSeconds % 60; + if (m === 0) return `${s}s`; + return `${m}m ${String(s).padStart(2, '0')}s`; +} + /* ------------------------------------------------------------------ */ /* Provider Card */ /* ------------------------------------------------------------------ */ From d79211b4ad3c24008e483d24207ca8e83e669901 Mon Sep 17 00:00:00 2001 From: deeqdev Date: Sat, 7 Feb 2026 18:17:28 +0300 Subject: [PATCH 5/5] Fix CI: increase dashboard page test timeout to 30s The first page load in CI triggers Next.js compilation which can exceed the default 5s Vitest timeout. Increase to 30s to match the other web E2E test timeouts. Co-authored-by: Cursor --- test/web.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web.test.ts b/test/web.test.ts index bb1da6b..cc64922 100644 --- a/test/web.test.ts +++ b/test/web.test.ts @@ -196,5 +196,5 @@ describe('Web UI — page', () => { expect(res.status).toBe(200); const html = await res.text(); expect(html).toContain('Task Sync'); - }); + }, 30_000); });