diff --git a/.dev.vars.example b/.dev.vars.example index ee6cfa1..1a5a6fd 100644 --- a/.dev.vars.example +++ b/.dev.vars.example @@ -9,3 +9,6 @@ CF_TOKEN=your_api_token_with_zone_email_permissions # Legacy Global API Key alternative. Use this only instead of CF_TOKEN. # CF_EMAIL=you@example.com # CF_API_KEY=your_global_api_key + +# The Worker uses the AI binding in wrangler.jsonc. Override its chat/draft model if needed. +# AI_MODEL=@cf/moonshotai/kimi-k2.5 diff --git a/AGENTIC.md b/AGENTIC.md new file mode 100644 index 0000000..2b20bf7 --- /dev/null +++ b/AGENTIC.md @@ -0,0 +1,251 @@ +# Agentic email and MCP implementation plan + +Status: initial implementation added September 24, 2026. The feature contract and acceptance checklist below describe the intended complete design. The implementation status at the end distinguishes shipped code from remaining work. No build or validation commands were run, per the project's `AGENTS.md` instruction. + +## Goal and recommended scope + +Add a built-in email assistant, automatic draft replies for eligible inbound mail, and an MCP server for external assistants. Reuse Mailflare's mail storage, composer, search, permissions, and delivery services. Every agent-originated send must require a human to review and explicitly approve the exact outgoing message. + +Recommended first release: + +- A mailbox-aware side panel with nine email tools, persistent private conversations, streaming responses, visible tool activity, and links into the existing composer. +- Opt-in auto-drafting per mailbox, with one designated reviewer and durable background jobs. +- An authenticated `/mcp` endpoint exposing the same mail operations, plus mailbox discovery and draft updates. External assistants request a send review; approval happens in Mailflare. +- Cloudflare and Docker support through shared application services and runtime-specific model/queue adapters. + +Keep autonomous sending, arbitrary third-party MCP connections, attachment interpretation, web browsing, permanent email deletion, bulk actions, and collaborative shared chat out of the first release. + +## What the reference actually implements + +Source snapshot: Cloudflare's [agentic-inbox at commit `48039bb`](https://github.com/cloudflare/agentic-inbox/tree/48039bb6785af34e592c2966f87cde2b255c4c80). The implementation is more precise than the README's description of “9 email tools … and sending.” + +| Area | Observed behavior | Adaptation for Mailflare | +| --- | --- | --- | +| Built-in agent | `EmailAgent` extends `AIChatAgent`; nine tools, none of which sends email. Uses Workers AI, streaming chat, and a five-step limit. | Keep the nine-tool draft-first workflow; provide explicit send review through the UI. | +| Auto-draft | Inbound storage triggers `/onNewEmail` through `ctx.waitUntil`. The agent reads the email/thread, uses fresh model context, and saves a draft. | Use recoverable jobs after successful intake; do not make model availability part of mail delivery. | +| Prompt and history | Mailbox prompt settings live in R2; agent history lives in a mailbox-named Durable Object. | Store settings/history in D1 or SQLite; private conversations belong to a user and mailbox. | +| Draft quality | Scans inbound/thread text for prompt injection and uses a second model to remove commentary from drafts. | Treat scans as optional defense; enforce authority in code and reject malformed/empty drafts. | +| MCP | `EmailMCP` registers 13 tools, including direct send and permanent deletion. Shared helper functions serve both MCP and chat. | Share one authorized service layer; omit permanent deletion and replace direct sending with review requests. | +| Authentication | Cloudflare Access is the single trust boundary; authorized users can operate on all mailboxes. | Preserve Mailflare's account, delegation, disabled-account, and mailbox checks on every operation. | +| UI | Agent panel displays streaming Markdown, tool activity, and a composer handoff; MCP panel displays connection information. | Follow existing Mailflare components, navigation, and composer behavior. | + +These observations come from the pinned [agent implementation](https://github.com/cloudflare/agentic-inbox/blob/48039bb6785af34e592c2966f87cde2b255c4c80/workers/agent/index.ts), [MCP implementation](https://github.com/cloudflare/agentic-inbox/blob/48039bb6785af34e592c2966f87cde2b255c4c80/workers/mcp/index.ts), [shared tools](https://github.com/cloudflare/agentic-inbox/blob/48039bb6785af34e592c2966f87cde2b255c4c80/workers/lib/tools.ts), [inbound handler](https://github.com/cloudflare/agentic-inbox/blob/48039bb6785af34e592c2966f87cde2b255c4c80/workers/index.ts), [authentication/routing](https://github.com/cloudflare/agentic-inbox/blob/48039bb6785af34e592c2966f87cde2b255c4c80/workers/app.ts), and [agent UI](https://github.com/cloudflare/agentic-inbox/blob/48039bb6785af34e592c2966f87cde2b255c4c80/app/components/AgentPanel.tsx). + +Important distinction: the reference MCP send descriptions ask the caller to obtain confirmation, but their handlers accept message content and call delivery without a server-side approval record. That does not enforce the approval requirement needed here. Its built-in agent avoids this by having no send tool. + +## Existing Mailflare foundations and integration gaps + +| Existing area | Relevant files | Implication | +| --- | --- | --- | +| Next.js/React UI | `src/app/(dashboard)/layout.tsx`, `src/components/messages/message-split-layout.tsx` | Mount one agent provider/panel in the dashboard shell; avoid duplicating it for each folder. | +| Composer and drafts | `src/components/compose/`, `src/app/api/drafts/`, `src/app/(dashboard)/drafts/` | AI drafts should be ordinary `messages` rows with `status = draft`, supplemented with provenance. | +| Draft ownership | `src/app/api/drafts/utils.ts`, `src/app/api/drafts/[id]/utils.ts` | Draft APIs currently enforce individual ownership. Auto-drafts need an explicit reviewer, not a mailbox-wide shared draft assumption. | +| Mailbox access and sender identity | `src/lib/mailboxes/access.ts`, `src/lib/email/sender.ts` | Reuse read/send/manage capabilities, domain identities, and send-on-behalf display rules. Admin status does not automatically grant mailbox access here. | +| Read/thread/search | `src/lib/email/inbound.ts`, `src/lib/email/thread-view.ts`, `src/lib/search/`, `src/app/api/messages/route.ts` | Reuse authorized reads and search conditions. Thread lookup currently takes a message ID and caps results at 200. | +| Inbound processing | `worker.ts`, `src/lib/email/inbound.ts`, `src/lib/email/intake.ts` | Attach auto-draft scheduling to the common processor used by Cloudflare and Node/relay intake. | +| Existing automatic replies | `src/lib/email/auto-reply.ts` | Out-of-office replies already send automatically; AI auto-drafts must be a separate setting and execution path. | +| Delivery | `src/app/api/send/route.ts`, `src/lib/email/send.ts` | Immediate sends call the provider directly; scheduled sends use `OUTBOUND_QUEUE`. An approval/deduplication layer must precede either path. | +| External API authentication | `src/lib/api/key-auth.ts`, `src/lib/api/scopes.ts` | Reuse key hashing/authentication, but add MCP-specific scopes and mailbox restrictions. Existing scopes are `send`, `read`, `jmap`, and `domains`. | +| Realtime | `src/lib/realtime/`, `src/hooks/message-realtime-utils.ts` | Extend the event union and query invalidation for draft/job changes; current notifications are `new_message`. | +| Runtime portability | `src/lib/runtime.ts`, `server/runtime/env.ts`, `server/runtime/queue.ts`, `server/runtime/scheduler.ts` | Node queue timers are in-memory. Persist AI jobs and recover them after restart. | +| Schema/deployment | `src/db/schema/index.ts`, `drizzle/migrations/`, `wrangler*.jsonc`, `env.d.ts` | Add additive migrations, model configuration, queue bindings, and documented runtime configuration. | + +There are no AI SDK/Agents/MCP dependencies in the inspected `package.json`. + +## Feature details + +### 1. Built-in assistant panel + +- Add an Assistant button beside the existing dashboard header controls. Open a resizable right panel on desktop and an accessible drawer on narrow screens; preserve room for message details and the floating composer. +- Display the selected mailbox and optional selected-email context above chat. Require a specific mailbox when the user is viewing an aggregate inbox. Switching mailboxes switches conversation scope and cancels the old stream. +- Provide starter actions such as “Summarize this thread,” “Find emails about…,” and “Draft a reply.” Only fetch full message bodies when needed. Reading through the assistant must not implicitly mark mail as read. +- Persist conversations per `(userId, mailboxId, conversationId)`. Include streamed text, tool status, errors, and draft references. Other delegates must not see private chat history. +- Offer stop, retry, new conversation, and delete history. Store the user turn before starting generation; persist tool effects/results as they happen. On interruption, show the saved partial result and reconcile completed tools before retrying. +- Show tools as readable activity cards, for example “Reading thread” and “Draft created.” Show failure reasons without dumping raw headers, storage keys, or credentials. +- Draft cards offer Open in composer, Regenerate, and Discard. Regenerate creates a new suggestion or requires explicit replacement; it must not overwrite a human-edited draft. +- Render chat Markdown without raw HTML or automatic remote images. Generate email text separately from chat commentary; convert text safely for the existing rich-text composer and apply signatures once. + +### 2. Nine built-in email tools + +Use one schema/handler registry with adapters for the model and MCP. Chat binds its mailbox on the server; the model cannot switch it by inventing an argument. MCP accepts a mailbox ID and validates it against the authenticated principal's allowed mailboxes. + +| Tool | Inputs and result | Required behavior | +| --- | --- | --- | +| `list_emails` | Folder/status filter, cursor, bounded limit; returns metadata and next cursor. | Authorized mailbox only; default 20, maximum 50. Show only the caller's drafts. | +| `get_email` | Message ID; returns text body, metadata, attachment metadata, and truncation information. | Verify message belongs to the active mailbox; omit attachment bytes and internal storage keys. | +| `get_thread` | Anchor message ID; returns chronological messages and continuation/truncation information. | Adapt existing thread service; enforce mailbox and draft-owner boundaries on every returned row. | +| `search_emails` | Query, optional folder, cursor; returns snippets and IDs. | Reuse Mailflare search semantics and indexes; no model-supplied SQL. | +| `draft_email` | Recipients, subject, body; returns draft ID and composer link. | Require sender permission; resolve From server-side. Save under the requesting user. | +| `draft_reply` | Source message ID, body, reply/reply-all mode; returns linked draft. | Derive recipients and RFC threading headers server-side. Handle Reply-To, aliases, and self-address exclusion using shared reply logic. | +| `mark_email_read` | Message ID and read boolean; returns updated state. | Apply existing Mailflare mutation permissions; add unread support in the shared service if needed. | +| `move_email` | Message ID and allowed destination; returns updated state. | Reuse existing move/status policy. Validate custom-folder ownership. Never manufacture sent/draft state or permanently delete. | +| `discard_draft` | Draft ID and expected revision; returns discarded state. | Caller-owned draft only; require an explicit user action before discarding human-edited content. | + +Read tools require `canRead`; draft creation requires `canSendOnBehalf` or stronger and the existing sender resolver. For read/move mutations, carry through the actual existing per-operation permission checks rather than treating readable mail as universally writable. Each handler rechecks access when it runs, including after a long model response. + +Return structured results such as IDs, operation status, revision, pagination, and safe display text. Use stable errors such as `not_found`, `permission_denied`, `conflict`, `rate_limited`, and `provider_unavailable`. + +### 3. Explicit send approval + +The nine model tools cannot invoke `sendEmail`, enqueue outbound jobs, approve messages, or change scheduling. A user asking “send it” in chat opens a review action; natural-language assent is not a reusable authorization token. + +1. Persist a draft revision before presenting approval. Show From, To, CC, BCC, subject, body, attachment list, and any scheduled delivery time. +2. Create a pending approval bound to the user, mailbox, draft ID/revision, and a server-calculated hash of the complete outgoing payload, including attachment identifiers/content hashes and threading headers. +3. An authenticated browser action labeled **Confirm and send** approves that snapshot. Require same-origin/CSRF protection and fresh mailbox/sender authorization. MCP keys and model tools cannot call the approval action successfully. +4. Atomically claim the approval and create one durable delivery command with a unique approval ID. Reject expired, cancelled, already-claimed, or stale approvals. Editing the draft or attachments invalidates approval and requires a new review. +5. Dispatch that immutable command through the existing delivery service. Record the resulting message/job ID and consume the draft only after a durable handoff. Repeated clicks return the existing delivery status. + +Use a short approval lifetime, initially 15 minutes. Treat scheduled AI mail the same way; changing its schedule requires renewed approval. Do not expose automatic scheduling in the initial agent tools. + +The current provider-send path is not an exactly-once transaction with the database. Add command-level deduplication, and handle a timeout after provider acceptance as an **unknown delivery outcome** requiring reconciliation; do not blindly retry and risk duplicate email. Audit the reviewer separately from the AI run/MCP key that proposed the draft. + +Apply this flow to the AI draft card, MCP review requests, and AI drafts opened in the composer. Enforce the draft provenance/revision checks server-side in the relevant send entry points. Existing manual-email and out-of-office workflows retain their current semantics; MCP credentials must not have legacy `send`, `jmap`, or wildcard privileges that could bypass review. + +### 4. Auto-draft on inbound email + +Settings: enabled (default off), custom writing instructions, designated reviewer (default mailbox owner), allowed folders, optional sender filters, and daily draft limit. Only the mailbox owner or a delegate with management permission may change mailbox-wide settings. A reviewer must be enabled and retain read/send permission. + +Initial eligibility: newly received, successfully stored inbound messages in the configured inbox/folders. Skip spam, trash, blocked senders, imported historical messages, self-mail, mailing lists, bounces, no-reply senders, and automatic responses. Reuse/extract the existing auto-reply header checks without changing out-of-office behavior. Skip suspicious messages or failed spam analysis by default. Default to skipping AI auto-drafts when mailbox out-of-office replies are enabled; explain that policy in settings. + +Processing flow: + +1. Complete MIME parsing, routing, spam classification, thread assignment, and attachment storage. +2. Persist an eligible auto-draft job with a unique `(mailboxId, sourceMessageId, jobKind)` key. Persist enough intake state to repair the gap between mail storage and job creation. A queue payload carries only the job ID. +3. Dispatch independently of inbound delivery. Claim jobs with an expiring lease and bounded retries; Cloudflare Queue redelivery and Node restarts must not create duplicate drafts. +4. Recheck mailbox configuration, source status, reviewer access, existing replies/drafts, and daily budget. Serialize work per mailbox/thread and mark obsolete jobs superseded when newer messages make them stale. +5. Read the latest message and bounded recent thread context into a fresh model run, independent of private chat history. Signal truncation; do not claim to have read the full conversation when limits were reached. +6. Give this background run only source/thread read access and permission to create one reply draft for that source. It cannot search unrelated conversations, move mail, delete drafts, choose arbitrary recipients, or send. +7. Validate structured draft output, sanitize it, resolve recipients/threading deterministically, and save a standard draft owned by the reviewer with AI provenance. Do not turn arbitrary model commentary into a fallback email. +8. Publish a draft-ready event to the reviewer, with an inbox badge/card linking to the composer. Sending follows the approval flow above. Record skipped/failed jobs with a reason and an explicit retry action. + +Keep all mail delivery successful even if generation, scanning, notification, or queue dispatch fails. A durable recovery sweep must recover pending dispatches and expired leases. The current inbound duplicate check returns immediately for an already stored `(mailboxId, rawR2Key)`; modify that branch narrowly to reconcile missing AI work, otherwise retries can permanently miss auto-drafts. Avoid triggering work for historical/imported messages during recovery by persisting an intake eligibility marker. R2 writes and SQL writes are not one transaction: represent incomplete intake explicitly and reconcile before marking a job ready. + +Initial limits: one active generation per mailbox, five model steps per interactive turn, at most 20 recent thread messages and a bounded text/token budget, three background attempts with backoff, and 25 automatic drafts per mailbox per day. Make these configurable server-side and tune them from measured use. + +### 5. MCP integration + +Expose an MCP **server** for external clients; the built-in assistant calls the shared service layer directly. Connecting the built-in assistant to arbitrary external MCP servers is separate future work. + +- Serve `/mcp` over HTTPS with the official SDK's Streamable HTTP implementation. Prefer request-scoped/stateless transport. Pin a supported protocol/SDK combination and document client compatibility instead of hand-writing JSON-RPC or copying the reference's transport unchanged. MCP transport/version semantics have evolved; consult the [transport specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports) during implementation. +- MVP authentication: dedicated revocable Bearer API keys for clients that support custom headers. Reuse key verification, but require an explicit MCP credential kind/opt-in and mailbox allowlist; existing keys gain no new rights automatically. This is a limited compatibility mode, not a claim of universal OAuth-based MCP compatibility. +- Add `mcp:read`, `mcp:draft`, `mcp:organize`, and `mcp:request-send` scopes. Require the matching scope plus current user/mailbox permission. `list_mailboxes` returns the intersection of current access and the key allowlist. Carry key ID in auth context for revocation/auditing; it is absent from the current `ApiAuthResult`. +- Reject mixed MCP/legacy-send/JMAP/wildcard credentials in this mode. Do not accept browser session cookies as MCP authentication. Never put keys in URLs or chat messages. +- Expose the nine shared tools plus `list_mailboxes`, `update_draft` with expected revision, `request_send`, and `get_send_request`. Map each tool to its scope, and check scope in execution as well as discovery. +- `request_send(draftId, expectedRevision)` returns `pending_approval`, an approval ID, and a Mailflare review URL. `get_send_request` returns pending/sent/failed/expired status to the originating authorized principal. Neither operation grants approval or sends directly. Document this intentional difference from upstream `send_email`/`send_reply`. +- Validate Origin when present, bound inputs/results and request rates, and authenticate every request. If compatibility requires stateful sessions, bind each session to the same user/key and recheck access; a session ID is not authorization. +- Add a connection screen showing the endpoint, supported client/auth mode, selected mailboxes, scopes, last use, and revocation. Show newly generated secrets only once; generated examples use placeholders after that. + +OAuth discovery/authorization for clients without header-based credential setup is a later milestone. Follow the applicable MCP authorization specification rather than passing session cookies or Cloudflare admin tokens to clients. + +## Architecture and storage + +Recommended architecture is a portable AI SDK service using existing Next.js streaming routes, with D1/SQLite persistence. Use Workers AI on Cloudflare and a configured server-side HTTP model provider on Docker. This avoids introducing a second mailbox database or two different chat persistence implementations. + +Cloudflare's `AIChatAgent` is a valid alternative when Durable Object persistence and resumable streaming are the priority; those capabilities are provided by [`@cloudflare/ai-chat`](https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/). It would require a separate Node adapter and additional authorization around agent instance routing. Defer that dependency in the initial portable implementation; automatic stream resumption is not promised by the proposed plain HTTP streaming approach. + +```text +Assistant panel -> authenticated chat route -> bounded model/tool loop +External client -> /mcp + scoped API key ----> shared authorized mail tools + | + existing D1/SQLite + R2/files + +Inbound processor -> durable AI job -> runtime dispatcher -> reply draft +Human review -> revision-bound approval -> durable send command -> delivery +``` + +Proposed additive schema: + +| Entity | Main fields / constraints | +| --- | --- | +| `mailbox_agent_settings` | Mailbox PK, enabled, autoDraftEnabled, reviewerUserId, instructions, version, filters, dailyLimit, updatedAt. | +| `agent_conversations` | ID, userId, mailboxId, title, createdAt, updatedAt; scoped history index. | +| `agent_chat_messages` | Conversation ID, sequence, role, validated parts JSON, runId, status; unique sequence and client message ID for deduplication. | +| `agent_runs` | Actor/reviewer, mailbox, conversation/source message, mode, status, model, settings version, timestamps, token usage, safe error; atomic budget reservation. | +| `agent_jobs` | Source message/mailbox, kind, reviewer, status, attempts, nextAttemptAt, leaseUntil, runId, draftId, skip reason; unique inbound job key. | +| `agent_draft_metadata` | Draft message PK, origin (`chat`, `auto`, `mcp`), source message/run/key, revision, humanEditedAt, stale/superseded marker. | +| `agent_send_approvals` | Principal, mailbox, draft/revision, payload hash/snapshot reference, expiry, status, reviewedBy, commandId; single-use claim. | +| `agent_send_commands` | Unique approval ID, immutable payload reference, dispatch state, lease, message/job ID, delivery outcome and timestamps. | +| API key extension | Credential kind and mailbox allowlist, preferably a join table; scopes remain explicit. | +| Intake recovery marker | Source message, intake completion, eligibility/settings snapshot, and dispatch reconciliation state. | + +Reuse `messages` for draft bodies and existing attachment storage; do not duplicate email bodies in job payloads. Add revision tracking on every AI draft mutation path, including composer autosave and attachments. Personal manual drafts need not acquire AI approval semantics. + +Delete conversation history on user request; use a configurable retention policy (proposed default 30 days for completed chat/run details). Keep minimal audit/approval records according to the existing installation's retention policy. Include new tables in backup/restore; pause recovered jobs after restore until configuration and reviewer access have been reconciled. + +## Implementation map + +New code should follow the repository convention: helper functions in separate files in the same folder (`utils.ts` or purpose-specific modules), type definitions in adjacent `types.d.ts`. Inspect and preserve all manual edits before touching existing integration points; do not reformat adjacent code. + +| Location | Planned responsibility | +| --- | --- | +| `src/lib/agent/` | Tool registry/schemas, authorized adapters, model loop/provider adapter, prompt construction, context limits, run persistence, draft validation. | +| `src/lib/agent/jobs/` | Eligibility, durable job claims, generation, reconciliation, budget handling; adjacent types. | +| `src/lib/agent/approvals/` | Snapshot hashing, revision checks, browser approval, durable delivery commands. | +| `src/lib/mcp/` | Server registration, transport adapter, dedicated-key authentication, scope enforcement, MCP result formatting. | +| `src/app/mcp/route.ts` | Thin Next.js entry point for `/mcp`; keep compatible with OpenNext and Node. | +| `src/app/api/agent/` | Chat stream, conversations/history, settings, job retry/status, send review/confirmation endpoints. | +| `src/components/agent/` | Provider, toggle/panel, chat messages, tool cards, draft cards, approval UI, settings and connection details. | +| Existing dashboard/composer/settings files | Mount UI, provide selected-mail context, open generated drafts, route AI draft sends through review. | +| Existing draft/read/search/send services | Extract only the functionality needed for reuse; preserve established permissions and manual behavior. | +| `src/lib/email/inbound.ts`, `worker.ts`, `worker-utils.ts` | Intake/recovery hook and explicit dispatch of AI jobs. Current worker fallback treats unknown queue payloads as outbound: replace that ambiguity before adding a job kind. | +| `server/runtime/` | Durable-job startup recovery and periodic dispatcher; HTTP model configuration; retain current email transport adapters. | +| `src/lib/realtime/`, relevant hooks | Draft/job event types and authorized notifications/query invalidation on both runtimes. | +| Schema/migrations/config/docs | Additive schema, AI queue and provider configuration, MCP scopes/key UI, deployment and self-hosting instructions. | + +Dependencies to evaluate and pin during implementation: `ai`, its React integration, `workers-ai-provider`, an HTTP model-provider adapter, the official MCP TypeScript SDK, and a Markdown renderer if needed. Mailflare uses Zod 4; the reference uses Zod 3, so do not copy its dependency versions or tool typings blindly. No model/provider credentials enter client bundles. On Cloudflare, add the [`AI` binding](https://developers.cloudflare.com/workers-ai/configuration/bindings/) and a dedicated agent queue; use a frequent recovery schedule alongside the existing backup cron. Docker uses persisted jobs with its local scheduler and server-only provider credentials. Missing AI configuration disables generation gracefully; authorized MCP reads/draft CRUD can still work without a model. + +## Delivery sequence and acceptance criteria + +These are acceptance criteria for the feature; they have not been verified in this change. + +### Phase 1 — Authorized mail service and persistence + +Add schema, principal context, dedicated MCP credentials, revision-aware draft operations, and the shared nine-tool registry. Resolve reply identity/threading and reviewer ownership before wiring a model. + +Acceptance: inaccessible mailbox/message IDs never return data; read-only delegates cannot draft/send; each delegate's personal drafts remain private; aliases and send-on-behalf identity follow existing behavior; stale revisions fail without overwriting user edits. + +### Phase 2 — Interactive agent and review flow + +Add provider configuration, bounded tool loop, private persistent history, panel/composer integration, approval snapshots, and durable send commands. Make the feature opt-in and initially enable it for a single mailbox. + +Acceptance: users can summarize/search/draft with visible tool results; history survives refresh; cancellation leaves recoverable state; no model tool sends; edits invalidate approval; repeated approval clicks create one command; provider-acceptance uncertainty never causes blind resend. Verify usability on narrow and wide layouts during implementation. + +### Phase 3 — MCP + +Add transport and connection settings using the shared services. Document the supported protocol versions and authentication mode with actual client examples once integration is exercised. + +Acceptance: scoped clients discover and use allowed tools; revoked/disabled principals lose access; cross-mailbox IDs and legacy send endpoints cannot bypass scope; a send request remains pending until a Mailflare browser approval; MCP reading/drafting works without a configured AI model. + +### Phase 4 — Auto-drafting + +Add mailbox settings, intake eligibility/reconciliation, background generation, notifications, skip reasons, retry, and daily limits. Roll out to a small mailbox set before enabling broadly. + +Acceptance: one eligible inbound message produces at most one initial AI draft; queue redelivery/restart does not duplicate it; a crash between storage and dispatch is recovered; spam/imports/automatic responses are skipped; newer thread activity and human edits are preserved; failed generation does not fail inbound mail; auto-drafts never enqueue outgoing email. + +### Phase 5 — Runtime and rollout completion + +Complete Cloudflare and Docker configuration, model budgets, usage/error visibility, retention, backup/restore handling, and operational docs. Exercise the authorization, approval, retry, and interruption scenarios above when implementation work is authorized; avoid unrelated cleanup. + +Acceptance: both deployments support the documented feature set, with helpful disabled states when providers are unavailable. Disabling the feature stops new runs/dispatches, expires pending agent approvals, and leaves existing drafts editable. Already confirmed delivery commands require an explicit cancellation decision rather than silently disappearing. + +## Proposed defaults and later decisions + +- Enable nothing automatically on upgrade. Mailbox owners explicitly enable the assistant/auto-drafts and choose a reviewer. +- Keep AI instructions additive to mandatory server policy; custom prompts cannot grant tools, broaden mailbox access, or remove approval. +- Treat all email content and tool results as untrusted data. Only the app establishes recipients, authority, and approval. Model-based injection scans cannot replace these controls. +- Show the configured provider and explain that selected email/thread content is sent to it when enabling AI. Log IDs, timing, usage, and error categories rather than message bodies or credentials. +- Choose the production model after evaluating tool calling, reply quality, latency, and cost. The reference's Kimi model is a research observation, not a required or permanently available default. +- Later decisions: OAuth MCP compatibility, attachment analysis, shared team draft ownership, optional Cloudflare `AIChatAgent` transport, and broader model-provider selection. None should weaken explicit approval for agent-originated delivery. + +## Implementation status (September 24, 2026) + +The initial implementation adds the mailbox-scoped assistant panel, nine shared email tools, private persisted chat with streaming text and tool activity, AI draft provenance, reviewer-bound send approval, automatic reply-draft jobs, and a Bearer-key MCP server. Agent send requests use a 15-minute payload snapshot and an authenticated browser confirmation. The database prevents two approvals for one draft from both reaching claimed or delivered status. The composer routes agent drafts through review; model tools and MCP tools have no direct send operation. Cloudflare and Node runtime configuration, migrations, and operator documentation are included. All settings default off. + +The current implementation is **not yet a verified production release**. The following parts of the design remain incomplete or need integration exercise: + +- No build, typecheck, migration exercise, runtime test, or browser review was run, as instructed by `AGENTS.md`. Validate the MCP SDK adapter, AI provider adapters, Cloudflare queue binding, Node startup, and migration before deployment. +- Auto-draft jobs are durable and lease-based, with one active lease per mailbox and a daily completed-draft limit. There is no separate atomic model-cost reservation, and an unusually long generation that outlives its lease still needs reconciliation. The current plain-text model output is checked for emptiness and common commentary prefixes, not parsed as a structured reply. +- There is no separate immutable send-command/outbox table. A claimed approval calls the existing delivery service once, and an uncertain provider outcome remains `unknown` for manual reconciliation. This avoids automatic duplicate retries but does not provide transactional exactly-once delivery. +- The assistant panel has a fixed width. Auto-draft cards can open, request a new draft through chat, or discard the current draft; richer tool progress, mobile accessibility review, and multi-mailbox selection in the MCP key UI remain to be completed. The MCP key API supports explicit scopes and mailbox allowlists; the UI supports scope selection for the current mailbox. +- Optional sender/folder filters, suspicious-content scanning, intake-completion markers, model usage audit/budget reservation, and restore-time pause/reconciliation are not implemented. Default eligibility is limited to newly received inbox mail and skips known automated/self/spam/error cases. +- MCP uses scoped Bearer keys for clients that can set headers. OAuth-based client authorization remains a later milestone. diff --git a/docs/api.md b/docs/api.md index 3c425eb..9f0660d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,6 +1,6 @@ # API and integrations -Mailflare exposes APIs for domain management and sending email. Authentication and mailbox permissions still apply to these routes. +Mailflare exposes APIs for domain, account, and mailbox management, and for sending email. Authentication and mailbox permissions still apply to these routes. ## Domain management @@ -18,7 +18,7 @@ The hostname must be the apex of a zone available to the configured Cloudflare c ### Domain management over the API -The same operations are available to scripts through API keys with the `domains` scope, using `Authorization: Bearer `: +The same operations are available to scripts through admin API keys with the `domains` scope, using `Authorization: Bearer `. Create these keys in Admin > API keys. The owner must retain the admin role; personal mail keys from Settings cannot grant domain access. | Mailflare route | Purpose | | --- | --- | @@ -31,6 +31,24 @@ The same operations are available to scripts through API keys with the `domains` `GET /api/v1/domains` returns `{ domains, dns }`, where `dns[id].auth` reports `ok` / `missing` / `unknown` for MX, SPF, DKIM and DMARC. `GET /api/v1/domains/[id]/dns` returns the full audit, including the names queried and the values found. The `setup` route provisions MX/SPF through Email Routing, DKIM through the sending subdomain, and a `v=DMARC1; p=none` TXT for DMARC; on a self-hosted install where DNS is managed manually it returns an error, since Mailflare cannot write the zone. +## Account and mailbox management + +Admin > API keys can also grant the `accounts` and `mailboxes` scopes independently. These routes use `Authorization: Bearer ` and require the key owner to retain the admin role. Account management requires a Team license; creating a shared mailbox also requires a Team license. Each key can access only accounts created by its owner and mailboxes owned by those accounts or the admin. + +Admin API keys cannot read or send mail. Enable **Allow MCP access** when creating an admin key to use its selected `domains`, `accounts`, and `mailboxes` permissions through `/mcp`. The `manage_domains`, `manage_accounts`, and `manage_mailboxes` tools expose the corresponding management actions below. Admin MCP keys do not expose mail tools. Use Settings > API keys for mail and mail MCP access. + +| Scope | Mailflare route | Purpose | +| --- | --- | --- | +| `accounts` | `GET /api/v1/accounts` | List managed accounts | +| `accounts` | `POST /api/v1/accounts` | Create an account and its mailbox (`{ username, domainId, password, role? }`) | +| `accounts` | `GET /api/v1/accounts/[id]` | Get a managed account | +| `accounts` | `PATCH /api/v1/accounts/[id]` | Update an account (`{ name, role, disabled, canManageMailboxes, forwardingEmail?, password? }`) | +| `mailboxes` | `GET /api/v1/mailboxes` | List managed mailboxes | +| `mailboxes` | `POST /api/v1/mailboxes` | Create a mailbox (`{ domainId, localPart, displayName?, type?, ownerUserId? }`) | +| `mailboxes` | `GET /api/v1/mailboxes/[id]` | Get a managed mailbox | +| `mailboxes` | `PATCH /api/v1/mailboxes/[id]` | Update mailbox settings | +| `mailboxes` | `DELETE /api/v1/mailboxes/[id]` | Delete a mailbox and its routing rule | + ## Sending email Send email through `POST /api/v1/send`. `to`, `cc` and `bcc` accept either a comma-separated header string or an array of addresses; each entry may carry a display name (`"Maya Chen" `). A message can reach up to 50 recipients across the three fields. Attachments are optional and use Base64-encoded content: @@ -74,7 +92,7 @@ The dashboard composer accepts up to 10 attachments, with a 10 MB limit per file ## JMAP -Mailflare serves [JMAP](https://jmap.io) (RFC 8620 core and RFC 8621 mail, plus submission) so external mail apps can read and send mail. Discovery is at `/.well-known/jmap`, which redirects to `/jmap/session`. Authenticate with an API key that has the `jmap` scope, either as `Authorization: Bearer ` or as the password of HTTP Basic auth (the username is ignored). Settings > Account > Email apps mints such a key. +Mailflare serves [JMAP](https://jmap.io) (RFC 8620 core and RFC 8621 mail, plus submission) so external mail apps can read and send mail. Discovery is at `/.well-known/jmap`, which redirects to `/jmap/session`. Authenticate with an API key that has the `jmap` scope, either as `Authorization: Bearer ` or as the password of HTTP Basic auth (the username is ignored). Settings > Account > App passwords mints such a key. The account id is the user id. Each Mailflare mailbox appears as a top-level JMAP Mailbox with system children (`inbox`, `drafts`, `sent`, `archive`, `junk`, `trash`) and one child per user folder; a message belongs to exactly one of them. Supported methods: `Mailbox/get|query|set` (folders only), `Thread/get`, `Email/get|query|set|import`, `SearchSnippet/get`, `Identity/get`, `EmailSubmission/set`, and `Core/echo`. `Email/copy` and `Email/parse` are not implemented. `*/changes` return `cannotCalculateChanges`, so clients re-query on a state change; `/jmap/eventsource` pushes state changes by polling. `Email/set` creates drafts, updates `$seen` and `$flagged`, moves between mailboxes, and destroys (to Trash first, then permanently). `EmailSubmission/set` sends a draft and reports it destroyed, since the sent copy is a new message. Blob download and upload follow the Session's `downloadUrl` and `uploadUrl`. diff --git a/docs/deployment.md b/docs/deployment.md index 859b396..4ef6bd1 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -79,6 +79,14 @@ Deploy the complete Worker with `npm run deploy` whenever the cron trigger is ad After upgrading an existing installation and confirming the cron trigger is active, the old Workflow can be removed with `npx wrangler workflows delete mailflare-database-backup`. Deleting it also removes its historical Workflow instances; backup files in R2 and rows in Mailflare's backup history are unaffected. +## Email assistant and MCP + +The assistant uses the Workers AI `AI` binding and a separate `mailflare-agent` queue. Provision the queue in the Cloudflare account before deploying a configuration that declares it, and apply migration `0032_add_agentic_mail.sql` before opening the new UI on an existing database. The five-minute cron recovers pending auto-draft work; the 02:00 UTC cron still runs backups. + +In the inbox, open **Assistant → Settings** for a mailbox, select its reviewer, and enable the assistant. Auto-drafting is a separate opt-in. It skips spam, automated mail, and mailboxes with out-of-office replies enabled. Generated replies appear as ordinary drafts assigned to the reviewer. The reviewer must open the draft and confirm the exact content before delivery. + +The assistant panel no longer exposes MCP key management. External MCP clients can still connect to `https:///mcp` with a mailbox-scoped Bearer key created through the authenticated `/api/agent/mcp-keys` endpoint. Keys can be listed and revoked through that endpoint; a new key is shown only once. The server uses Streamable HTTP and accepts clients that can set a Bearer header. Its `request_send` tool returns a Mailflare review URL; the MCP key cannot confirm or deliver messages directly. MCP does not require Workers AI for read and draft tools. + ## Updating Mailflare The **Update Mailflare** button in the admin dashboard dispatches `.github/workflows/deploy-update.yml` in the installation repository. The workflow replaces the installation branch's complete tracked tree with the latest upstream source, commits that replacement, and pushes it. This avoids merge conflicts between independently created installation and upstream histories. Target-only committed files and code changes are intentionally removed; repository variables, secrets, and other GitHub or Cloudflare configuration remain unchanged. A connected Cloudflare Git integration then builds and deploys the change. diff --git a/docs/self-hosting.md b/docs/self-hosting.md index 651580b..2234ba6 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -79,6 +79,9 @@ and the DNS page shows what to set by hand. | `CF_ACCOUNT_ID`, `CF_TOKEN` | unset | Cloudflare Email Sending, and zone management if the token allows | | `INBOUND_WEBHOOK_SECRET` | unset | Enables `/api/inbound` for the relay Worker | | `TURNSTILE_SECRET_KEY` | unset | Bot protection on login and reset forms (`NEXT_PUBLIC_TURNSTILE_SITE_KEY` at build time) | +| `AI_BASE_URL` | unset | OpenAI-compatible model API base URL for the assistant | +| `AI_API_KEY` | unset | Server-only key for that model API | +| `AI_MODEL` | `gpt-4o-mini` | Model ID supported by the configured API | ## Operations @@ -92,6 +95,12 @@ and the DNS page shows what to set by hand. before it is queued, so a restart never loses a message; at worst one stays unparsed until it is re-imported. +## Email assistant and MCP + +Set `AI_BASE_URL`, `AI_API_KEY`, and `AI_MODEL` in the container environment to configure the built-in assistant. These values stay on the server. Assistant chat is available by default when a provider is configured; a mailbox manager can change its writing instructions and availability through the settings button in the assistant panel. Automatic reply drafts remain off until enabled there. Auto-draft work is recorded in SQLite and retried after a restart by the local scheduler. AI failure does not reject inbound mail. Out-of-office auto-replies and AI auto-drafts are separate features; turn off out-of-office replies before enabling auto-drafts for a mailbox. + +The MCP endpoint is `/mcp`. Create a dedicated mailbox-scoped Bearer key in **Assistant → MCP** and give the key to a client that supports custom HTTP headers. The endpoint uses Streamable HTTP; `request_send` gives the client a review URL, and only an authenticated Mailflare browser session can confirm delivery. MCP read and draft tools remain available when no AI model is configured. + ## Running without Docker ```bash diff --git a/drizzle/migrations/0032_add_agentic_mail.sql b/drizzle/migrations/0032_add_agentic_mail.sql new file mode 100644 index 0000000..0d92b28 --- /dev/null +++ b/drizzle/migrations/0032_add_agentic_mail.sql @@ -0,0 +1,29 @@ +CREATE TABLE `mailbox_agent_settings` (`mailbox_id` text PRIMARY KEY NOT NULL REFERENCES `mailboxes`(`id`) ON DELETE cascade, `enabled` integer NOT NULL DEFAULT 1, `auto_draft_enabled` integer NOT NULL DEFAULT 0, `reviewer_user_id` text REFERENCES `users`(`id`) ON DELETE set null, `instructions` text NOT NULL DEFAULT '', `daily_limit` integer NOT NULL DEFAULT 25, `updated_at` integer NOT NULL); +--> statement-breakpoint +CREATE TABLE `agent_conversations` (`id` text PRIMARY KEY NOT NULL, `user_id` text NOT NULL REFERENCES `users`(`id`) ON DELETE cascade, `mailbox_id` text NOT NULL REFERENCES `mailboxes`(`id`) ON DELETE cascade, `title` text NOT NULL DEFAULT 'New conversation', `created_at` integer NOT NULL, `updated_at` integer NOT NULL); +--> statement-breakpoint +CREATE INDEX `agent_conversations_owner_idx` ON `agent_conversations` (`user_id`, `mailbox_id`, `updated_at`); +--> statement-breakpoint +CREATE TABLE `agent_chat_messages` (`id` text PRIMARY KEY NOT NULL, `conversation_id` text NOT NULL REFERENCES `agent_conversations`(`id`) ON DELETE cascade, `role` text NOT NULL, `content` text NOT NULL, `tool_name` text, `created_at` integer NOT NULL); +--> statement-breakpoint +CREATE INDEX `agent_chat_messages_conversation_idx` ON `agent_chat_messages` (`conversation_id`, `created_at`); +--> statement-breakpoint +CREATE TABLE `agent_jobs` (`id` text PRIMARY KEY NOT NULL, `mailbox_id` text NOT NULL REFERENCES `mailboxes`(`id`) ON DELETE cascade, `source_message_id` text NOT NULL REFERENCES `messages`(`id`) ON DELETE cascade, `reviewer_user_id` text NOT NULL REFERENCES `users`(`id`) ON DELETE cascade, `status` text NOT NULL DEFAULT 'pending', `attempts` integer NOT NULL DEFAULT 0, `next_attempt_at` integer NOT NULL, `lease_until` integer, `draft_id` text REFERENCES `messages`(`id`) ON DELETE set null, `reason` text, `created_at` integer NOT NULL); +--> statement-breakpoint +CREATE UNIQUE INDEX `agent_jobs_source_idx` ON `agent_jobs` (`mailbox_id`, `source_message_id`); +--> statement-breakpoint +CREATE INDEX `agent_jobs_due_idx` ON `agent_jobs` (`status`, `next_attempt_at`); +--> statement-breakpoint +CREATE TABLE `agent_draft_metadata` (`draft_id` text PRIMARY KEY NOT NULL REFERENCES `messages`(`id`) ON DELETE cascade, `mailbox_id` text NOT NULL REFERENCES `mailboxes`(`id`) ON DELETE cascade, `origin` text NOT NULL, `source_message_id` text REFERENCES `messages`(`id`) ON DELETE set null, `revision` integer NOT NULL DEFAULT 1, `human_edited_at` integer, `created_at` integer NOT NULL); +--> statement-breakpoint +CREATE TABLE `agent_send_approvals` (`id` text PRIMARY KEY NOT NULL, `draft_id` text NOT NULL REFERENCES `messages`(`id`) ON DELETE cascade, `mailbox_id` text NOT NULL REFERENCES `mailboxes`(`id`) ON DELETE cascade, `user_id` text NOT NULL REFERENCES `users`(`id`) ON DELETE cascade, `request_key_id` text REFERENCES `api_keys`(`id`) ON DELETE set null, `revision` integer NOT NULL, `payload_hash` text NOT NULL, `status` text NOT NULL DEFAULT 'pending', `claimed_at` integer, `message_id` text, `expires_at` integer NOT NULL, `created_at` integer NOT NULL); +--> statement-breakpoint +CREATE INDEX `agent_send_approvals_user_idx` ON `agent_send_approvals` (`user_id`, `status`); +--> statement-breakpoint +CREATE UNIQUE INDEX `agent_send_approvals_delivery_idx` ON `agent_send_approvals` (`draft_id`) WHERE `status` IN ('claimed', 'sent', 'unknown'); +--> statement-breakpoint +CREATE TABLE `mcp_key_mailboxes` (`key_id` text NOT NULL REFERENCES `api_keys`(`id`) ON DELETE cascade, `mailbox_id` text NOT NULL REFERENCES `mailboxes`(`id`) ON DELETE cascade); +--> statement-breakpoint +CREATE UNIQUE INDEX `mcp_key_mailbox_idx` ON `mcp_key_mailboxes` (`key_id`, `mailbox_id`); +ALTER TABLE `api_keys` ADD `kind` text NOT NULL DEFAULT 'legacy'; +--> statement-breakpoint diff --git a/drizzle/migrations/0033_add_agent_provider_settings.sql b/drizzle/migrations/0033_add_agent_provider_settings.sql new file mode 100644 index 0000000..95968ef --- /dev/null +++ b/drizzle/migrations/0033_add_agent_provider_settings.sql @@ -0,0 +1,9 @@ +ALTER TABLE `app_settings` ADD `agent_provider` text; +--> statement-breakpoint +ALTER TABLE `app_settings` ADD `agent_preset` text; +--> statement-breakpoint +ALTER TABLE `app_settings` ADD `agent_base_url` text; +--> statement-breakpoint +ALTER TABLE `app_settings` ADD `agent_api_key` text; +--> statement-breakpoint +ALTER TABLE `app_settings` ADD `agent_model` text; diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index d872e63..25e00bc 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -225,6 +225,20 @@ "when": 1788850800003, "tag": "0031_add_password_reset_and_mfa", "breakpoints": true + }, + { + "idx": 32, + "version": "6", + "when": 1788850800004, + "tag": "0032_add_agentic_mail", + "breakpoints": true + }, + { + "idx": 33, + "version": "6", + "when": 1788850800005, + "tag": "0033_add_agent_provider_settings", + "breakpoints": true } ] } diff --git a/env.d.ts b/env.d.ts index fbbe42d..6b7be85 100644 --- a/env.d.ts +++ b/env.d.ts @@ -3,6 +3,11 @@ interface CloudflareEnv { EMAIL: SendEmail; BUCKET: R2Bucket; INBOUND_QUEUE: Queue; + AGENT_QUEUE?: Queue<{ kind: "agent.draft"; jobId: string }>; + AI?: Ai; + AI_MODEL?: string; + AI_BASE_URL?: string; + AI_API_KEY?: string; // The outbound queue also carries webhook retries so that scheduled redelivery needs no extra binding. OUTBOUND_QUEUE: Queue< | import("./src/lib/email/send").OutboundQueueMessage @@ -15,6 +20,7 @@ interface CloudflareEnv { import("./src/lib/realtime/hub").RealtimeHub >; LOGIN_RATE_LIMIT?: RateLimit; + AGENT_RATE_LIMIT?: RateLimit; CF_TOKEN?: string; CF_API_KEY?: string; CF_EMAIL?: string; diff --git a/media/__pycache__/video_utils.cpython-313.pyc b/media/__pycache__/video_utils.cpython-313.pyc new file mode 100644 index 0000000..edb542d Binary files /dev/null and b/media/__pycache__/video_utils.cpython-313.pyc differ diff --git a/media/human-and-agent/STORYBOARD.md b/media/human-and-agent/STORYBOARD.md new file mode 100644 index 0000000..7dc88b5 --- /dev/null +++ b/media/human-and-agent/STORYBOARD.md @@ -0,0 +1,19 @@ +# Mailflare — Good company for your inbox + +15 seconds · 1920 × 1080 · 60 fps · original synthesized stereo soundtrack + +| Time | Picture | Copy | +| --- | --- | --- | +| 0–2.5s | Oversized type rises from masks. A blue envelope opens into a smiling note. Orbital accents and a lime notification pop on the beat. | Email. With a smile. | +| 2.5–5s | Warm human portrait and blue agent glyph bounce into a split composition, connected by a moving message. | For humans. And agents. | +| 5–10s | A stylized Mailflare inbox assembles. An agent drafts a reply; the cursor selects Review & send, then confirms delivery. | Feels familiar. Does more. / Draft. Review. Send. | +| 10–12.5s | A custom-domain address slides into a giant pill. Graphic rules orbit a confident ownership statement. | Your domain. Your rules. | +| 12.5–15s | Envelope wipe resolves into the Mailflare mark and a spacious cobalt end card. | mailflare / Email for humans. And their agents. / Make room for better email. | + +## Product grounding + +Read `README.md`, `mailflare-landing/app/page.tsx`, `docs/api.md`, and `src/components/agent/send-review.tsx`, and viewed the existing inbox screenshot and app icon before writing the copy. The UI in the film is an animated product illustration with fictional sample mail. The human confirmation step reflects the implementation's review-before-send flow. + +## Render + +Run `python3 render.py` from this directory. Requires Pillow and FFmpeg. Functions are in `utils.py`. No application files or previous video assets are modified. diff --git a/media/human-and-agent/__pycache__/utils.cpython-313.pyc b/media/human-and-agent/__pycache__/utils.cpython-313.pyc new file mode 100644 index 0000000..1f38d13 Binary files /dev/null and b/media/human-and-agent/__pycache__/utils.cpython-313.pyc differ diff --git a/media/human-and-agent/mailflare-human-and-agent.mp4 b/media/human-and-agent/mailflare-human-and-agent.mp4 new file mode 100644 index 0000000..dfc5821 Binary files /dev/null and b/media/human-and-agent/mailflare-human-and-agent.mp4 differ diff --git a/media/human-and-agent/poster.png b/media/human-and-agent/poster.png new file mode 100644 index 0000000..cb8bb58 Binary files /dev/null and b/media/human-and-agent/poster.png differ diff --git a/media/human-and-agent/render.py b/media/human-and-agent/render.py new file mode 100644 index 0000000..0455f50 --- /dev/null +++ b/media/human-and-agent/render.py @@ -0,0 +1,3 @@ +from utils import render + +render() diff --git a/media/human-and-agent/scene-01.png b/media/human-and-agent/scene-01.png new file mode 100644 index 0000000..176c13d Binary files /dev/null and b/media/human-and-agent/scene-01.png differ diff --git a/media/human-and-agent/scene-04.png b/media/human-and-agent/scene-04.png new file mode 100644 index 0000000..3c70410 Binary files /dev/null and b/media/human-and-agent/scene-04.png differ diff --git a/media/human-and-agent/scene-07.png b/media/human-and-agent/scene-07.png new file mode 100644 index 0000000..35b343b Binary files /dev/null and b/media/human-and-agent/scene-07.png differ diff --git a/media/human-and-agent/scene-11.png b/media/human-and-agent/scene-11.png new file mode 100644 index 0000000..98de60b Binary files /dev/null and b/media/human-and-agent/scene-11.png differ diff --git a/media/human-and-agent/scene-14.png b/media/human-and-agent/scene-14.png new file mode 100644 index 0000000..cb8bb58 Binary files /dev/null and b/media/human-and-agent/scene-14.png differ diff --git a/media/human-and-agent/soundtrack.wav b/media/human-and-agent/soundtrack.wav new file mode 100644 index 0000000..c129eb6 Binary files /dev/null and b/media/human-and-agent/soundtrack.wav differ diff --git a/media/human-and-agent/utils.py b/media/human-and-agent/utils.py new file mode 100644 index 0000000..92f2fd9 --- /dev/null +++ b/media/human-and-agent/utils.py @@ -0,0 +1,480 @@ +import math +import random +import struct +import subprocess +import wave +from functools import lru_cache +from pathlib import Path + +from PIL import Image, ImageDraw, ImageFont + + +ROOT = Path(__file__).resolve().parent +W, H, S, FPS = 1920, 1080, 2, 60 +CREAM = '#F5F3EB' +INK = '#172824' +BLUE = '#2458ED' +LIME = '#D9F876' +PEACH = '#F8AF8E' +MUTED = '#72817B' +WHITE = '#FFFFFF' +FONT = '/System/Library/Fonts/Supplemental/Arial.ttf' +BOLD = '/System/Library/Fonts/Supplemental/Arial Bold.ttf' + + +def clamp(v): + return max(0, min(1, v)) + + +def ease(v): + return 1 - (1 - clamp(v)) ** 4 + + +def smooth(v): + v = clamp(v) + return v * v * (3 - 2 * v) + + +def pop(v): + v = clamp(v) + return 1 + 2.1 * (v - 1) ** 3 + 1.1 * (v - 1) ** 2 + + +def mix(a, b, v): + return a + (b - a) * v + + +@lru_cache(maxsize=120) +def font(size, bold=False): + return ImageFont.truetype(BOLD if bold else FONT, round(size * S)) + + +def txt(im, x, y, value, size, color=INK, bold=False, anchor='lt'): + ImageDraw.Draw(im).text((round(x * S), round(y * S)), value, font=font(size, bold), fill=color, anchor=anchor, stroke_width=0) + + +def box(im, coords, fill, radius=0, outline=None, width=1): + ImageDraw.Draw(im).rounded_rectangle(tuple(round(v * S) for v in coords), radius=round(radius * S), fill=fill, outline=outline, width=round(width * S)) + + +def line(im, coords, color, width=1): + ImageDraw.Draw(im).line(tuple(round(v * S) for v in coords), fill=color, width=max(1, round(width * S)), joint='curve') + + +def circle(im, x, y, r, fill=None, outline=None, width=1): + ImageDraw.Draw(im).ellipse(tuple(round(v * S) for v in (x-r, y-r, x+r, y+r)), fill=fill, outline=outline, width=max(1, round(width * S))) + + +def poly(im, points, fill): + ImageDraw.Draw(im).polygon([(round(x * S), round(y * S)) for x, y in points], fill=fill) + + +def arc(im, bounds, start, end, fill, width=3): + ImageDraw.Draw(im).arc(tuple(round(v*S) for v in bounds), start, end, fill=fill, width=round(width*S)) + + +def new(bg=CREAM): + return Image.new('RGB', (W, H), bg) + + +def layer(): + return Image.new('RGBA', (W, H), (0, 0, 0, 0)) + + +def paste(im, item, x=0, y=0, scale=1, rotation=0, opacity=1): + if scale != 1: + item = item.resize((max(1, round(item.width*scale)), max(1, round(item.height*scale))), Image.Resampling.BICUBIC) + if rotation: + item = item.rotate(rotation, Image.Resampling.BICUBIC, expand=True) + if opacity < 1: + item = item.convert('RGBA') + item.putalpha(item.getchannel('A').point(lambda a: int(a*clamp(opacity)))) + im.paste(item, (round(x*S-item.width/2), round(y*S-item.height/2)), item if item.mode == 'RGBA' else None) + + +def reveal(im, x, y, value, size, t, delay=0, color=INK, bold=True): + p = ease((t-delay)/0.65) + if p <= 0: + return + patch = Image.new('RGBA', (W, round(size*1.32*S))) + txt(patch, 0, (1-p)*size*1.45, value, size, color, bold) + im.paste(patch, (round(x*S), round(y*S)), patch) + + +def spark(im, x, y, r, color=BLUE, rot=0): + points = [] + for i in range(8): + a = rot + math.pi*i/4 + rr = r if i%2 == 0 else r*0.25 + points.append((x+math.cos(a)*rr, y+math.sin(a)*rr)) + poly(im, points, color) + + +def check(im, x, y, size, color=INK): + line(im, (x-size*.5, y, x-size*.1, y+size*.4, x+size*.65, y-size*.45), color, max(1.5,size*.15)) + + +def envelope(im, x, y, w, color=BLUE, opened=0): + h = w*.7 + box(im, (x-w/2,y-h/2,x+w/2,y+h/2), color, w*.08) + poly(im, [(x-w*.45,y-h*.43),(x,y-h*.43-w*.28*opened),(x+w*.45,y-h*.43),(x,y+h*.13)], '#96CBFF') + if opened: + yy = y-w*.3*opened + box(im,(x-w*.32,yy-h*.22,x+w*.32,yy+h*.27),CREAM,w*.03) + circle(im,x-w*.1,yy,w*.016,INK) + circle(im,x+w*.1,yy,w*.016,INK) + arc(im,(x-w*.09,yy,x+w*.09,yy+w*.10),0,180,INK,w*.015) + poly(im,[(x-w*.5,y-h*.39),(x+w*.03,y+h*.12),(x-w*.5,y+h*.5)],'#22B7E4') + poly(im,[(x+w*.5,y-h*.39),(x-w*.04,y+h*.12),(x+w*.5,y+h*.5)],'#0493D1') + poly(im,[(x-w*.5,y+h*.5),(x,y-h*.03),(x+w*.5,y+h*.5)],'#1EA7DF') + + +def brand(im, x=42, y=33, dark=False): + envelope(im,x+10,y+8,21) + txt(im,x+30,y-2,'mailflare',18,WHITE if dark else INK,True) + + +def footer(im, index, dark=False): + color = '#A7BFFE' if dark else MUTED + txt(im,42,505,'GOOD COMPANY FOR YOUR INBOX',9,color,True) + txt(im,918,505,f'0{index} / 05',9,color,True,'rt') + + +def portrait(im,x,y,r,t,agent=False): + if agent: + box(im,(x-r,y-r,x+r,y+r),BLUE,r*.3) + box(im,(x-r*.65,y-r*.32,x+r*.65,y+r*.47),CREAM,r*.22) + for dx in [-.28,.28]: + box(im,(x+r*dx-r*.06,y-r*.1,x+r*dx+r*.06,y+r*.13),INK,r*.05) + line(im,(x,y-r*.36,x,y-r*.67),CREAM,3) + circle(im,x,y-r*.73,r*.1,LIME) + arc(im,(x-r*.21,y+r*.07,x+r*.21,y+r*.3),0,180,INK,2) + else: + circle(im,x,y,r,PEACH) + arc(im,(x-r*.48,y-r*.77,x+r*.50,y+r*.1),185,343,INK,r*.19) + for dx in [-.26,.26]: + circle(im,x+r*dx,y-r*.01,r*.045,INK) + arc(im,(x-r*.27,y-r*.03,x+r*.27,y+r*.44),0,180,INK,r*.05) + circle(im,x-r*.47,y+r*.22,r*.10,'#EE8A72') + circle(im,x+r*.47,y+r*.22,r*.10,'#EE8A72') + + +def opening(t): + im=new() + brand(im) + footer(im,1) + # A faint oversized orbit gives the typography a physical stage. + circle(im,760,267,197,None,'#E0E4D8',1) + circle(im,760,267,159,None,'#E0E4D8',1) + reveal(im,54,150,'Email.',102,t) + reveal(im,54,266,'With a smile.',65,t,.22) + p=ease((t-.55)/.6) + txt(im,57,366+18*(1-p),'A little more human. A lot more possible.',17,MUTED) + card=Image.new('RGBA',(560,540)) + # Drawing helpers use the same two-pixel unit inside local assets. + envelope(card,140,145,184,opened=ease((t-.55)/.8)) + paste(im,card,748,281+math.sin(t*3)*7,max(.01,pop(t/.8)),rotation=-9+4*math.sin(t*2)) + spark(im,635,131,23*pop((t-.4)/.5),BLUE,t*.5) + spark(im,873,385,17*pop((t-.6)/.5),BLUE,-t*.7) + q=pop((t-.85)/.45) + circle(im,849,180,26*q,LIME) + if q>.5: + txt(im,849,181,'1',22,INK,True,'mm') + line(im,(57,419,149+42*ease((t-.75)/.7),419),BLUE,4) + return im + + +def partners(t): + im=new() + box(im,(480,0,960,540),'#E9EEFF') + brand(im) + footer(im,2) + p=pop(t/.6) + portrait(im,254,219+12*math.sin(t*3),67*p,t) + portrait(im,706,219-12*math.sin(t*3),67*pop((t-.12)/.6),t,True) + for k in range(19): + xx=354+k*14 + circle(im,xx,220,1.7,'#A9B8A9') + e=layer() + envelope(e,480,220,53) + xx=mix(364,597,(math.sin(t*2.8-1.5)+1)/2) + im.paste(e,(round((xx-480)*S),round(9*math.sin(t*5)*S)),e) + reveal(im,89,332,'For humans.',49,t,.08) + reveal(im,525,332,'And agents.',49,t,.26,BLUE) + txt(im,254,414,'Your people. Your conversations.',14,MUTED,anchor='mt') + txt(im,706,414,'A helping hand, built right in.',14,'#687CA5',anchor='mt') + spark(im,359,113,13,BLUE,t) + spark(im,823,302,14,BLUE,-t) + return im + + +def cursor(im,x,y,pressed=False): + poly(im,[(x+2,y+3),(x+2,y+26),(x+9,y+20),(x+15,y+31),(x+21,y+27),(x+14,y+17),(x+25,y+15)],'#182924') + poly(im,[(x,y),(x,y+22),(x+7,y+16),(x+14,y+28),(x+18,y+25),(x+11,y+14),(x+22,y+13)],WHITE) + if pressed: + circle(im,x+2,y+3,18,None,LIME,2) + + +def inbox(t): + im=new('#E7EDFF') + brand(im) + footer(im,3) + reveal(im,46,80,'Feels familiar. Does more.',40,t,0) + ui=layer() + box(ui,(60,152,900,477),'#CFD8F1',19) + box(ui,(54,144,894,469),WHITE,19) + box(ui,(54,144,220,469),'#F4F7FF',19) + envelope(ui,80,169,18) + txt(ui,98,160,'mailflare',14,INK,True) + box(ui,(70,199,200,237),'#DBE7FF',12) + txt(ui,89,210,'+ Compose',13,BLUE,True) + for i,label in enumerate(['Inbox','Starred','Snoozed','Sent','Drafts']): + yy=265+i*32 + if i==0: + box(ui,(64,254,210,280),'#DEE8FD',12) + circle(ui,82,yy+2,3,BLUE if i==0 else '#ABB6C7') + txt(ui,96,yy-5,label,12,INK,i==0) + box(ui,(237,158,874,195),'#F1F4FA',17) + circle(ui,255,175,5,None,'#68798E',1.5) + line(ui,(259,179,263,183),'#68798E',1.5) + txt(ui,275,168,'Search your mail',12,'#8290A1') + txt(ui,243,215,'Inbox',20,INK,True) + txt(ui,870,219,'hello@your.studio',11,MUTED,anchor='rt') + entries=[('M','Maya','A little launch magic','Ready when you are.','#F8AF8E'),('J','Jamie','Coffee next week?','Tuesday sounds good.','#D9F876'),('S','Studio','The ideas are in','Three directions to explore.','#DBD2F9'),('A','Alex','That looks wonderful','Thanks for the lovely work.','#BDEBEE')] + for i,(initial,name,subject,body,color) in enumerate(entries): + p=ease((t-.13-i*.06)/.45) + y=252+i*50+(1-p)*20 + if i==0: + box(ui,(234,y-5,880,y+43),'#ECF1FF',9) + circle(ui,254,y+17,13,color) + txt(ui,254,y+17,initial,11,INK,True,'mm') + txt(ui,278,y+2,name,11,INK,True) + txt(ui,365,y+2,subject,12,INK,True) + txt(ui,365,y+20,body,10,MUTED) + if i>0: + line(ui,(240,y+43,878,y+43),'#EEF0F5') + p=ease(t/.7) + scale=.90+.10*p + paste(im,ui,480,270+(1-p)*110,scale) + # The agent's draft is staged as a readable foreground interaction. + a=ease((t-1)/.5) + if a>0: + panel=layer() + box(panel,(462,233,891,465),'#C0CCE8',17) + box(panel,(454,225,883,457),WHITE,17,outline='#D0DCFA') + circle(panel,478,248,12,BLUE) + spark(panel,478,248,7,LIME,math.pi/4) + txt(panel,499,239,'Your agent',14,INK,True) + box(panel,(773,237,865,260),'#EEF5E3',10) + txt(panel,819,244,'DRAFT READY',8,INK,True,'mt') + line(panel,(472,273,866,273),'#EBEEF3') + txt(panel,476,287,'Re: A little launch magic',12,INK,True) + sentence='Hi Maya, everything is ready for launch.' + count=round(len(sentence)*clamp((t-1.55)/.6)) + txt(panel,476,318,sentence[:count],12,'#58655F') + if t>1.95: + txt(panel,476,338,'Excited to share this with the world!',12,'#58655F') + line(panel,(472,375,866,375),'#EBEEF3') + txt(panel,476,399,'A draft from your agent. A final say from you.',10,MUTED) + box(panel,(713,419,864,447),BLUE,13) + txt(panel,788,428,'Review & send',11,WHITE,True,'mt') + paste(im,panel,480+28*(1-a),270+50*(1-a),opacity=a) + if t>2.4: + p=ease((t-2.4)/.55) + cursor(im,mix(913,811,p),mix(485,430,p),2.980 and t<4.18: + panel=layer() + box(panel,(498,280,870,454),CREAM,15,outline='#C9D8EC') + txt(panel,522,300,'Looks good?',23,INK,True) + txt(panel,522,338,'To Maya · Re: A little launch magic',12,MUTED) + box(panel,(677,396,846,433),BLUE,17) + txt(panel,760,408,'Confirm and send',12,WHITE,True,'mt') + paste(im,panel,480,270+16*(1-r),opacity=r) + cursor(im,815,417,3.88=4.18: + z=pop((t-4.18)/.35) + toast=layer() + box(toast,(595,371,856,440),LIME,20) + circle(toast,626,405,14,INK) + check(toast,626,405,10,LIME) + txt(toast,654,389,'Sent. Nice teamwork.',15,INK,True) + txt(toast,654,413,'One inbox. Better together.',10,INK) + paste(im,toast,480,270+18*(1-z),opacity=min(1,z)) + return im + + +def ownership(t): + im=new(LIME) + brand(im) + footer(im,4) + # Big concentric shapes stay outside the reading area. + for i in range(4): + circle(im,905,293,125+i*44+12*math.sin(t*1.4),None,'#B9D969',1) + reveal(im,55,128,'Your domain.',76,t) + reveal(im,55,222,'Your rules.',76,t,.15) + p=pop((t-.35)/.6) + tag=Image.new('RGBA',(1190,174)) + box(tag,(0,0,586,79),INK,39) + envelope(tag,42,39,31) + txt(tag,79,23,'hello@your.studio',31,CREAM,True) + circle(tag,547,39,16,LIME) + check(tag,547,39,12) + paste(im,tag,352,390+30*(1-p),max(.01,p),rotation=-2+1.5*ease(t/.8)) + p=ease((t-.7)/.5) + txt(im,62,458+12*(1-p),'Custom domains. Shared inboxes. You’re at home.',16,INK) + spark(im,799,154,48*pop(t/.6),BLUE,t*.6) + spark(im,855,362,21,BLUE,-t) + return im + + +def closing(t): + im=new(BLUE) + # Deliberately hold the completed end card for a comfortable reading beat. + for i in range(3): + circle(im,482,239,225+i*70+min(t,1)*8,None,'#3869EF') + p=pop(t/.6) + mark=Image.new('RGBA',(256,256)) + box(mark,(0,0,128,128),CREAM,32) + envelope(mark,64,72,87,opened=.5) + paste(im,mark,480,134,max(.01,p),rotation=-9*(1-ease(t/.7))) + # Centered title is masked upward with the same timing as the opening. + title='mailflare' + tw=ImageDraw.Draw(im).textlength(title,font=font(80,True))/S + reveal(im,(960-tw)/2,227,title,80,t,.15,CREAM) + q=ease((t-.3)/.65) + txt(im,480,343+(1-q)*24,'Email for humans. And their agents.',24,CREAM,False,'mt') + if t>.55: + b=pop((t-.55)/.5) + badge=Image.new('RGBA',(700,108)) + box(badge,(0,0,346,50),LIME,25) + txt(badge,173,16,'Make room for better email. ↗',17,INK,True,'mt') + paste(im,badge,480,427,max(.01,b)) + spark(im,202,252,19,CREAM,t*.25) + spark(im,761,189,27,LIME,-t*.25) + txt(im,480,505,'YOUR DOMAIN. YOUR INBOX.',10,'#BDCEFF',True,'mt') + return im + + +def frame(t): + scenes=[opening,partners,inbox,ownership,closing] + starts=[0,2.5,5,10,12.5] + index=max(i for i,start in enumerate(starts) if t>=start) + local=t-starts[index] + im=scenes[index](local) + # Fast traveling flap wipes bridge scenes like a message being handed over. + if index and local<.24: + p=clamp(local/.24) + previous=scenes[index-1](starts[index]-starts[index-1]+local) + edge=round(W*p) + im.paste(previous.crop((edge,0,W,H)),(edge,0)) + poly(im,[(960*p-55,0),(960*p+15,0),(960*p+75,270),(960*p+15,540),(960*p-55,540),(960*p+5,270)],BLUE if index!=4 else LIME) + return im + + +def soundtrack(): + sr=44100 + n=sr*15 + # Pure Python synthesis keeps this source portable without music assets. + left=[0.0]*n + right=[0.0]*n + rng=random.Random(29) + chords=[(146.832,184.997,220.0),(123.471,146.832,184.997),(97.999,123.471,146.832),(110.0,138.591,164.814)] + for beat in range(30): + start=int(beat*.5*sr) + notes=chords[(beat//8)%4] + # Warm, short kick and crisp offbeat snare. + for j in range(int(.22*sr)): + k=start+j + if k>=n: + break + s=j/sr + kick=.32*math.sin(2*math.pi*(49*s+6*(1-math.exp(-s*36))))*math.exp(-s*23) + snare=(rng.random()*2-1)*math.exp(-s*40)*.13 if beat%2 else 0 + v=kick+snare + left[k]+=v + right[k]+=v + for sub in range(2): + start2=start+int(sub*.25*sr) + for j in range(int(.055*sr)): + k=start2+j + if k>=n: + break + s=j/sr + v=(rng.random()*2-1)*math.exp(-s*85)*.035 + left[k]+=v*.7 + right[k]+=v + # Eighth-note bell motif; chord changes give the short film a finish. + for sub in range(2): + onset=start+int(sub*.25*sr) + freq=notes[(beat+sub)%3]*(4 if (beat+sub)%4==0 else 2) + for j in range(int(.36*sr)): + k=onset+j + if k>=n: + break + s=j/sr + env=min(1,s/.004)*math.exp(-s*13) + v=.065*env*(math.sin(2*math.pi*freq*s)+.25*math.sin(2*math.pi*freq*2*s)) + left[k]+=v*(.8 if sub else 1) + right[k]+=v*(1 if sub else .8) + echo=k+int(.125*sr) + if echo=n: + break + s=j/sr + v=.115*min(1,s/.008)*math.exp(-s*7)*math.sin(2*math.pi*freq*s) + left[k]+=v + right[k]+=v + for onset in [2.43,4.93,9.93,12.43]: + start=int(onset*sr) + for j in range(int(.25*sr)): + k=start+j + if k>=n: + break + u=j/(.25*sr) + v=(rng.random()*2-1)*math.sin(math.pi*u)**3*.10 + left[k]+=v*(1-u) + right[k]+=v*u + for onset,freq in [(9.2,880),(9.32,1174.66),(12.7,587.33)]: + for j in range(int(.4*sr)): + k=int(onset*sr)+j + if k 00:00:02,616 +Your business deserves a better address. + +2 +00:00:03,380 --> 00:00:06,259 +Mailflare gives you professional email +on your own domain. + +3 +00:00:07,350 --> 00:00:10,369 +Mailboxes, aliases, your whole team. +One place. + +4 +00:00:11,820 --> 00:00:13,910 +Your domain. Your email. Mailflare. diff --git a/media/launch-film/mailflare-launch.mp4 b/media/launch-film/mailflare-launch.mp4 new file mode 100644 index 0000000..1fd014a Binary files /dev/null and b/media/launch-film/mailflare-launch.mp4 differ diff --git a/media/launch-film/narration.wav b/media/launch-film/narration.wav new file mode 100644 index 0000000..feef6d2 Binary files /dev/null and b/media/launch-film/narration.wav differ diff --git a/media/launch-film/picture.mp4 b/media/launch-film/picture.mp4 new file mode 100644 index 0000000..a179604 Binary files /dev/null and b/media/launch-film/picture.mp4 differ diff --git a/media/launch-film/poster.png b/media/launch-film/poster.png new file mode 100644 index 0000000..1946ae2 Binary files /dev/null and b/media/launch-film/poster.png differ diff --git a/media/launch-film/render.mjs b/media/launch-film/render.mjs new file mode 100644 index 0000000..6330b60 --- /dev/null +++ b/media/launch-film/render.mjs @@ -0,0 +1,3 @@ +import { render } from './utils.mjs'; + +await render(); diff --git a/media/launch-film/scene-120.png b/media/launch-film/scene-120.png new file mode 100644 index 0000000..ae5767c Binary files /dev/null and b/media/launch-film/scene-120.png differ diff --git a/media/launch-film/scene-300.png b/media/launch-film/scene-300.png new file mode 100644 index 0000000..02b2f59 Binary files /dev/null and b/media/launch-film/scene-300.png differ diff --git a/media/launch-film/scene-540.png b/media/launch-film/scene-540.png new file mode 100644 index 0000000..0e6bbbd Binary files /dev/null and b/media/launch-film/scene-540.png differ diff --git a/media/launch-film/scene-840.png b/media/launch-film/scene-840.png new file mode 100644 index 0000000..1946ae2 Binary files /dev/null and b/media/launch-film/scene-840.png differ diff --git a/media/launch-film/score.wav b/media/launch-film/score.wav new file mode 100644 index 0000000..2c67b1a Binary files /dev/null and b/media/launch-film/score.wav differ diff --git a/media/launch-film/soundtrack.wav b/media/launch-film/soundtrack.wav new file mode 100644 index 0000000..99e7c9e Binary files /dev/null and b/media/launch-film/soundtrack.wav differ diff --git a/media/launch-film/speech-envelope.json b/media/launch-film/speech-envelope.json new file mode 100644 index 0000000..c9278bf --- /dev/null +++ b/media/launch-film/speech-envelope.json @@ -0,0 +1 @@ +[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0009754233869245476, 0.0012030609325900806, 0.0020193215907891777, 0.19211330921689335, 0.8380191153428334, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9959270192160958, 0.7624755218009072, 0.6582812572720708, 0.8358564132069453, 1, 1, 1, 1, 1, 1, 1, 1, 0.8884917890252773, 0.546390620561582, 0.6319392902289285, 1, 1, 1, 1, 1, 1, 1, 0.9107320461861799, 0.3776612382690502, 0.038898401958951556, 0.029892917985987436, 0.3730210052565194, 0.563533809096117, 1, 1, 1, 1, 1, 1, 1, 0.9894469966793475, 1, 1, 1, 1, 1, 1, 1, 1, 0.7147717910500613, 0.7639314060590138, 0.7306155898548958, 1, 0.9442269262139438, 0.8350058167091337, 1, 1, 1, 1, 0.8728672180923891, 0.6491559206032642, 0.545600215023877, 0.5558806514697515, 1, 1, 1, 1, 1, 0.6358138322453096, 0.9314743141076045, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.960192089418514, 0.67386139379399, 0.5412875916583522, 0.6586219825401325, 0.5106364228816225, 1, 1, 1, 1, 1, 1, 1, 0.754077792751721, 0.6319880526315843, 0.5997646224787345, 0.6617479199616992, 0.8119721776508416, 0.3028458240888362, 0.5651760185449868, 0.935915196297469, 0.9405880444716324, 0.7859477414332247, 0.6402626392905124, 0.6301560648722764, 0.5138704219932989, 0.4103966734965755, 0.30432006434596987, 0.11614839802427847, 0.03601429178233075, 0.009705660778910474, 0.0031243200605497057, 0.0011857713859116158, 0.0010198477385366816, 0.001057211121672428, 0.0010258860753502017, 0.001054844844525413, 0.0009674637835122644, 0.0009810624158578772, 0.0008921108149426602, 0.0009206172598806721, 0.0009002129489083952, 0.0008945913242590768, 0.0009637039736275448, 0.0009876244590288502, 0.0008518279639582123, 0.0009067361273917159, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0035126464862627552, 0.38219604218051256, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9524627656402184, 0.993441478956389, 0.8023639084068935, 0.7787311729601645, 0.7115121394308725, 0.39169755807726414, 0.31935850959914747, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.8733881726040662, 0.9547009952796546, 0.8440039522801006, 0.7990873457288121, 1, 1, 1, 1, 1, 0.9532343279862181, 0.9361220670197917, 0.8469417811389457, 0.7844103577603256, 1, 1, 1, 1, 0.8768015171817091, 0.44990394309102, 0.2259106000139392, 0.1805687806332842, 0.39326803225360296, 0.7146160029323562, 1, 1, 0.8858412200156448, 0.6709623570320776, 0.3911692519938163, 0.3622869352386557, 0.6571805056047216, 0.524370460625631, 1, 1, 1, 1, 1, 0.9732126723431095, 0.8838524509822984, 0.8671075324552429, 0.7926494020356957, 0.6668497022507961, 0.5307965129992981, 0.28187203489633833, 0.8777324225776594, 1, 1, 1, 1, 1, 1, 0.6893234612702103, 0.6492899336273944, 0.8131740988235412, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.8209089962034674, 0.5392023187274295, 0.5146328916952385, 0.893633822587456, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.926168868777922, 0.5699914990681298, 1, 1, 1, 1, 0.6189474042395622, 1, 1, 0.9049832391461078, 0.9994577380553104, 1, 1, 0.967976301855582, 0.9205034948049079, 0.8875160273356977, 0.6935914809236292, 0.7036654606469612, 0.6451985663624009, 0.5714996059960318, 0.5585976298810605, 0.38294375853813756, 0.19561181715168202, 0.12710302977002563, 0.06410016871031936, 0.02389783450799559, 0.004934167922535601, 0.002389881502266698, 0.001422234968453792, 0.0010603727650907206, 0.0009608131246789932, 0.0009837020719690919, 0.0009893892253644075, 0.0009658542285745979, 0.0010388563039374155, 0.0010876776950866404, 0.0010134943519996787, 0.0010106526210740166, 0.0010494675205742505, 0.0009141931348764438, 0.000680021762867795, 0.0007765471951513514, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0009139352306382661, 0.0007023279321414775, 0.02037455297388163, 0.9270235812969712, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.7011879581485336, 0.8220344066170664, 0.8174019798600235, 1, 1, 1, 1, 1, 1, 1, 1, 0.7567217045182911, 0.5156029580292595, 0.4335423773379302, 0.6869649262394395, 0.8905540508103799, 0.7648821575297838, 0.5959861671275675, 0.3848990273826381, 0.5930012764668922, 0.47680628157966914, 0.4012983961131799, 0.5292952301313911, 0.736744235345441, 0.7110534870722466, 0.2545472122144649, 0.43536488945738205, 0.1638741450934137, 0.5110665068667963, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.8611954320844543, 0.9928503980295408, 1, 0.9743920018364891, 0.8217610952548264, 0.6455966777344361, 0.4097784359928149, 0.31814991375513185, 0.5883592882724258, 0.704959283415101, 0.6466266695019129, 0.4886645077460495, 0.43208092962659184, 0.44560994870428083, 0.6199192208834289, 0.6782272741369605, 0.4115567480944441, 0.16183532460080768, 0.08025726299850651, 0.20639379844278322, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.7865510584246089, 0.4939380734416341, 0.48381832905705674, 0.511409746579803, 0.545361343686567, 0.5469141502586689, 1, 1, 1, 1, 1, 1, 1, 1, 0.6496593090610273, 0.26756179526109, 0.06863402809763847, 0.5911326548339364, 0.802320305627927, 0.7380003153012688, 0.47631590268456186, 0.4785455689172278, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.8876476521905053, 0.6857728912923106, 0.5183086531092851, 0.3451734296296157, 0.3741440462578608, 0.2402744371458198, 0.1472961823089512, 0.030183690039294876, 0.00288293004377822, 0.0012407971216800012, 0.0009105758283128499, 0.00651051475530483, 0.4663913185953022, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.44452666830273846, 0.36639299176650114, 0.23475504723565152, 0.19398956668196604, 1, 1, 1, 1, 1, 1, 1, 1, 0.6076701619053543, 0.38450467517079545, 0.6467763317166978, 0.6344802992056403, 0.631375365644906, 0.5505516458070335, 0.5225786429198196, 0.43679017669640946, 0.33971696475132834, 0.2437836846917863, 0.11992000396745245, 0.01350332691504706, 0.0017467268429712086, 0.0010487035032642486, 0.0009348471180789647, 0.0008479446511616228, 0.0009370635924260732, 0.0008996366449099719, 0.0009181557119996399, 0.0008796033478948907, 0.0008873946842229524, 0.0010132617230912504, 0.0011881943699152185, 0.0013408412903924889, 0.0008864378095472444, 0.0008896763856854837, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0024714850990055592, 0.001533265735173953, 0.0010114919949638832, 0.003774227468361692, 0.008947373013891797, 0.40125979084796615, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9825238521258115, 0.9759786960773581, 1, 1, 1, 1, 0.8831675424897789, 0.9903764181304804, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9426656873287416, 0.7898529073096112, 0.5758998683267827, 0.3642742282623912, 0.2010222248796853, 0.07127247009793695, 0.02982440949362144, 0.004841790615330105, 0.002391281798541275, 0.0013290803817922435, 0.0009387727383203942, 0.0008666434740218979, 0.0009759549396754102, 0.0009295364495592224, 0.0339099187098072, 0.20980291340669047, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.8979665926513435, 0.6319281730532106, 0.7881901561925932, 0.7728135135100388, 0.7021132307310125, 0.4850191818073292, 0.14445007716911726, 0.9358697337568127, 1, 1, 1, 1, 1, 1, 1, 1, 0.9523650242425075, 0.9540695946091853, 1, 0.984184518626763, 0.7118674200726193, 0.39892670673615527, 0.1511200576927231, 0.04282024338263303, 0.01080179592991832, 0.004817316188618344, 0.0012693473792804067, 0.0010070072494529293, 0.0009521869504915736, 0.0009906274506315971, 0.0008946440263313048, 0.0009138320485627876, 0.0008964866469859337, 0.0008819586677944789, 0.0009080351397299125, 0.001001278901379539, 0.001035310243452407, 0.0006885654310943303, 0.0008447693389469439, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] \ No newline at end of file diff --git a/media/launch-film/utils.mjs b/media/launch-film/utils.mjs new file mode 100644 index 0000000..fe6061d --- /dev/null +++ b/media/launch-film/utils.mjs @@ -0,0 +1,208 @@ +import { createRequire } from 'node:module'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { spawn } from 'node:child_process'; +import { once } from 'node:events'; + +const require = createRequire('/tmp/mailflare-film-tools/package.json'); +const { createCanvas, GlobalFonts } = require('@napi-rs/canvas'); +const ROOT = dirname(fileURLToPath(import.meta.url)); +const W = 1920, H = 1080, FPS = 60; +const C = { ink: '#111D3C', blue: '#185BFF', cyan: '#69DEFF', pale: '#F1F5FF', muted: '#7787A5', white: '#FFFFFF', navy: '#07122F', green: '#64EDB4' }; +GlobalFonts.registerFromPath(join(ROOT, '../../node_modules/next/dist/next-devtools/server/font/geist-latin.woff2'), 'Geist'); +GlobalFonts.registerFromPath('/System/Library/Fonts/Menlo.ttc', 'Mono'); +const canvas = createCanvas(W, H), ctx = canvas.getContext('2d'); +const voice = JSON.parse(readFileSync(join(ROOT, 'speech-envelope.json'))); + +export function clamp(v) { return Math.max(0, Math.min(1, v)); } +export function lerp(a, b, p) { return a + (b - a) * p; } +export function ease(v) { return 1 - (1 - clamp(v)) ** 4; } +export function smooth(v) { v = clamp(v); return v * v * (3 - 2 * v); } +export function spring(v) { v = clamp(v); return 1 - Math.exp(-7.5 * v) * Math.cos(9.5 * v); } +export function rr(x,y,w,h,r,fill,stroke=null,lw=1) { + ctx.beginPath(); ctx.roundRect(x,y,w,h,r); if(fill){ctx.fillStyle=fill;ctx.fill();} if(stroke){ctx.strokeStyle=stroke;ctx.lineWidth=lw;ctx.stroke();} +} +export function line(points,color,width=2) { + ctx.beginPath(); points.forEach(([x,y],i)=>i?ctx.lineTo(x,y):ctx.moveTo(x,y));ctx.strokeStyle=color;ctx.lineWidth=width;ctx.lineCap='round';ctx.lineJoin='round';ctx.stroke(); +} +export function ellipse(x,y,rx,ry,fill) {ctx.beginPath();ctx.ellipse(x,y,Math.max(.01,rx),Math.max(.01,ry),0,0,Math.PI*2);ctx.fillStyle=fill;ctx.fill();} +export function text(s,x,y,size=30,color=C.ink,weight=500,align='left') { + ctx.font=`${Math.round(weight/100)*100} ${size}px Geist`;ctx.textAlign=align;ctx.textBaseline='middle';ctx.fillStyle=color;ctx.fillText(s,x,y); +} +export function mono(s,x,y,size=22,color=C.muted,align='left') {ctx.font=`${size}px Mono`;ctx.textAlign=align;ctx.textBaseline='middle';ctx.fillStyle=color;ctx.fillText(s,x,y);} +export function gradient(x,y,x2,y2,colors) {const g=ctx.createLinearGradient(x,y,x2,y2);colors.forEach((c,i)=>g.addColorStop(i/(colors.length-1),c));return g;} +export function halo(x,y,r,color) {const g=ctx.createRadialGradient(x,y,0,x,y,r);g.addColorStop(0,color);g.addColorStop(1,`${color.slice(0,7)}00`);ctx.fillStyle=g;ctx.fillRect(x-r,y-r,r*2,r*2);} +export function shadow(alpha=.18,blur=50,dy=25) {ctx.shadowColor=`rgba(12,35,92,${alpha})`;ctx.shadowBlur=blur;ctx.shadowOffsetY=dy;} +export function clearShadow() {ctx.shadowColor='transparent';ctx.shadowBlur=0;ctx.shadowOffsetY=0;} +export function check(x,y,s,color=C.blue) {line([[x-s*.4,y],[x-s*.08,y+s*.3],[x+s*.5,y-s*.4]],color,s*.14);} +export function arrow(x,y,s,color=C.blue) {line([[x-s*.6,y],[x+s*.4,y]],color,3);line([[x,y-s*.4],[x+s*.4,y],[x,y+s*.4]],color,3);} +export function envelope(x,y,s,color=C.blue,open=0) { + ctx.save();ctx.translate(x,y);ctx.scale(s/100,s/100); + ctx.fillStyle=color;ctx.beginPath();ctx.moveTo(-47,-21);ctx.lineTo(0,-53-open*12);ctx.lineTo(47,-21);ctx.lineTo(47,39);ctx.lineTo(-47,39);ctx.closePath();ctx.fill(); + rr(-35,-25,70,56,4,'#FFF'); + ctx.fillStyle='#42C5EE';ctx.beginPath();ctx.moveTo(-47,-20);ctx.lineTo(47,39);ctx.lineTo(-47,39);ctx.fill(); + ctx.fillStyle='#139FD8';ctx.beginPath();ctx.moveTo(47,-20);ctx.lineTo(-47,39);ctx.lineTo(47,39);ctx.fill();ctx.restore(); +} +export function spark(x,y,s,color=C.blue,angle=0) {ctx.save();ctx.translate(x,y);ctx.rotate(angle);ctx.fillStyle=color;ctx.beginPath();for(let i=0;i<8;i++){let a=i*Math.PI/4,r=i%2?s*.23:s; i?ctx.lineTo(Math.cos(a)*r,Math.sin(a)*r):ctx.moveTo(Math.cos(a)*r,Math.sin(a)*r);}ctx.closePath();ctx.fill();ctx.restore();} +export function pill(s,x,y,w,dark=false,icon=false) {rr(x,y,w,48,24,dark?'#14264A':'#FFFFFFCC',dark?'#38527C':'#D9E3F7');if(icon){ellipse(x+24,y+24,4,4,C.green);}text(s,x+(icon?40:w/2),y+24,19,dark?'#BED4F9':C.ink,550,icon?'left':'center');} +export function reveal(s,x,y,size,t,delay=0,color=C.ink,weight=650,align='left') { + let p=ease((t-delay)/.62);if(p<=0)return;ctx.save();ctx.beginPath();ctx.rect(0,y-size*.7,W,size*1.45);ctx.clip();text(s,x,y+(1-p)*size*1.5,size,color,weight,align);ctx.restore(); +} +export function chrome(t,n,dark=false) { + const col=dark?'#F4F8FF':C.ink;ctx.save();ctx.globalAlpha=ease(t/.4);envelope(102,79,41);text('Mailflare',137,78,28,col,670);mono('MAKE IT YOURS',1817,79,16,dark?'#A2B9E8':C.muted,'right'); + text('PROFESSIONAL EMAIL. PERSONAL BY DESIGN.',83,1028,14,dark?'#7188B3':'#8A99B7',600);mono(`0${n} / 04`,1837,1028,16,dark?'#A2B9E8':C.muted,'right');ctx.restore(); +} +export function background(t,dark=false,blue=false) { + ctx.fillStyle=blue?'#1454F3':dark?C.navy:'#F4F7FE';ctx.fillRect(0,0,W,H); + if(blue){halo(300+80*Math.sin(t),150,1100,'#468CFF');halo(1670,1000,900,'#092681');} + else if(dark){halo(1000,650,850,'#123C8B');halo(1750,100,550,'#153151');} + else {halo(1550+60*Math.sin(t*.6),380,780,'#C6D8FF');halo(200,1000,650,'#DAEFFF');} + ctx.save();ctx.globalAlpha=dark?.1:.055;ctx.strokeStyle=dark?'#A2C3FF':'#3D73CA';ctx.lineWidth=1; + for(let x=-100;x.09){ellipse(0,-54,14+talk*5,4+talk*18,'#83DAFF');ellipse(0,-48+talk*2,8,3,'#E6FCFF');} + else {ctx.beginPath();ctx.ellipse(0,-61,19,13,0,.2,Math.PI-.2);ctx.strokeStyle='#82DCFF';ctx.lineWidth=5;ctx.stroke();} + ellipse(-95,-66,13,5,'#458BBD33');ellipse(95,-66,13,5,'#458BBD33'); + rr(-13,-252,26,52,12,gradient(-13,0,13,0,['#BFD3EF','#FFF','#ABBFE2'])); + ctx.save();ctx.shadowBlur=35;ctx.shadowColor='#53BFFF';ellipse(0,-255,19,19,gradient(-15,-271,18,-239,['#BAF6FF','#4ACDFF','#2588F3']));ctx.restore();ellipse(-6,-262,5,5,'#F2FFFF'); + ellipse(-156,-90,12,34,'#BDCEF0');ellipse(156,-90,12,34,'#8AA9DE'); + ctx.restore(); +} +export function floatMail(x,y,s,t,angle=0,dark=false) {ctx.save();ctx.translate(x,y+Math.sin(t*3+x)*8);ctx.rotate(angle);shadow(.16,35,13);rr(-s*.7,-s*.55,s*1.4,s*1.1,s*.22,dark?'#183864':'#FFFFFF');clearShadow();envelope(0,2,s*.75);ctx.restore();} +export function sceneIntro(t) { + background(t);chrome(t,1);let p=spring(t/.8); + ctx.save();ctx.translate(1510,590);ctx.rotate(-.35+t*.045);ctx.strokeStyle='#C5D8F5';ctx.lineWidth=2;ctx.beginPath();ctx.ellipse(0,0,325,260,0,0,Math.PI*2);ctx.stroke();ctx.restore(); + floatMail(1330,290,73,t,-.23);floatMail(1762,676,83,t,.2);spark(1730,332,24,C.blue,t*.35); + pill('YOUR NEXT CHAPTER',142,228,247); + reveal('You mean',136,357,121,t,.08);reveal('business.',136,489,121,t,.2,C.blue); + let a=ease((t-.7)/.55);ctx.save();ctx.globalAlpha=a;ctx.translate(0,(1-a)*50);shadow(.1,45,20);rr(140,604,1100,124,33,'#FFFFFF','#DDE6F8',1.5);clearShadow();envelope(202,669,52); + const swap=smooth((t-1.25)/.48);text('hello@',250,665,60,C.ink,580);ctx.save();ctx.beginPath();ctx.rect(442,619,630,100);ctx.clip();ctx.globalAlpha=1-swap;text('gmail.com',442,665-swap*75,60,'#8493AF',500);ctx.globalAlpha=swap;text('mailflare.co',442,742-swap*77,60,C.blue,620);ctx.restore(); + ctx.globalAlpha=a*swap;ellipse(1172,666,27,27,'#E1F7EE');check(1172,666,26,'#1B9B6D');ctx.restore(); + reveal('An address that’s unmistakably yours.',146,789,30,t,1.65,C.muted,450); + robot(1530,570+(1-p)*740,1.04*p,t,1); + const pulse=clamp((t-1.45)/.7);if(pulse>0&&pulse<1){ctx.save();ctx.globalAlpha=(1-pulse)*.6;ctx.strokeStyle=C.blue;ctx.lineWidth=2;ctx.beginPath();ctx.roundRect(140-pulse*25,604-pulse*25,1100+pulse*50,124+pulse*50,33+pulse*25);ctx.stroke();ctx.restore();} + mono('01 / OWN YOUR ADDRESS',147,887,18,'#6D86B2'); +} +export function search(x,y,w) {rr(x,y,w,42,12,'#F2F5FA');ctx.strokeStyle='#8190A9';ctx.lineWidth=2;ctx.beginPath();ctx.arc(x+23,y+19,6,0,Math.PI*2);ctx.stroke();line([[x+28,y+24],[x+33,y+29]],'#8190A9',2);text('Search your mail',x+48,y+21,16,'#8D99AD',450);mono('⌘ K',x+w-19,y+22,12,'#8D99AD','right');} +export function navIcon(index,x,y,color) { + ctx.save();ctx.translate(x,y); + if(index===0){rr(-9,-7,18,14,3,null,color,1.7);line([[-9,-1],[-3,-1],[-1,3],[3,3],[5,-1],[9,-1]],color,1.7);} + else if(index===1){const points=[];for(let i=0;i<10;i++){const a=i*Math.PI/5-Math.PI/2,r=i%2?4:10;points.push([Math.cos(a)*r,Math.sin(a)*r]);}points.push(points[0]);line(points,color,1.7);} + else if(index===2){line([[-10,-7],[10,0],[-10,8],[-5,0],[-10,-7]],color,1.7);line([[-5,0],[10,0]],color,1.7);} + else if(index===3){rr(-7,-10,14,20,2,null,color,1.7);line([[-3,-4],[3,-4]],color,1.7);line([[-3,1],[3,1]],color,1.7);line([[-3,6],[1,6]],color,1.7);} + else {rr(-9,-5,18,14,2,null,color,1.7);rr(-10,-9,20,5,1,null,color,1.7);line([[-3,1],[3,1]],color,1.7);} + ctx.restore(); +} +export function inbox(t) { + const w=1420,h=586;shadow(.17,65,30);rr(0,0,w,h,25,'#FFF','#D4DFF2',1.5);clearShadow(); + ctx.save();ctx.beginPath();ctx.roundRect(0,0,w,h,25);ctx.clip();rr(0,0,230,h,0,'#F4F7FC'); + envelope(37,39,31);text('Mailflare',64,39,23,C.ink,650);rr(20,89,188,49,14,'#175BFF');text('+ Compose',114,113,19,'#FFF',550,'center'); + const labels=['Inbox','Starred','Sent','Drafts','Archive'];for(let i=0;i<5;i++){if(i===0)rr(16,164+i*48,197,42,10,'#E2EBFF');navIcon(i,43,185+i*48,i===0?C.blue:'#7C8BA3');text(labels[i],68,185+i*48,18,i===0?C.blue:'#64718B',i===0?650:450);if(i===0)text(t>.8?'4':'3',190,185,15,C.blue,650,'right');} + text('MAILBOXES',29,455,11,'#8A96AC',650);ellipse(39,492,5,5,C.blue);text('hello@mailflare.co',54,492,15,'#627493');ellipse(39,531,5,5,'#5DC0A1');text('support@mailflare.co',54,531,15,'#627493'); + line([[230,0],[230,h]],'#DFE6F1',1);text('Inbox',258,39,27,C.ink,630);search(940,20,448);line([[230,76],[w,76]],'#E7ECF5',1); + const names=['Alex Morgan','Sam from Studio','Design team','Hieu Nguyen'];const subjects=['Let’s build something great.','Welcome to your new workspace','Brand assets, delivered.','Ready when you are.']; + const previews=['Everything you need to get started.','Your next big idea starts here.','The latest files are all here.','See you on the other side.']; + for(let i=0;i<4;i++){let p=ease((t-.2-i*.16)/.5);ctx.save();ctx.globalAlpha=p;ctx.translate((1-p)*160,0);const y=95+i*111;if(i===0)rr(244,y,519,102,14,'#EAF0FF');ellipse(275,y+26,5,5,i===0?C.blue:'#B6C4D9');text(names[i],295,y+24,17,C.ink,650);text(i===0?'Now':`${i*4}m`,740,y+24,13,'#8B98AC',450,'right');text(subjects[i],263,y+55,19,C.ink,520);text(previews[i],263,y+80,15,'#8794AA',420);ctx.restore();} + line([[780,76],[780,h]],'#E2E9F4',1);let r=ease((t-.65)/.7);ctx.save();ctx.globalAlpha=r;ctx.translate(0,(1-r)*35); + ellipse(839,126,23,23,'#CCE0FF');text('A',839,126,20,C.blue,650,'center');text('Alex Morgan',878,118,20,C.ink,650);text('alex@studio.co',878,144,14,'#8591A5',420);pill('TO YOU',1244,106,125); + text('Let’s build something great.',812,215,31,C.ink,600);text('Hey Hieu,',812,281,20,C.ink,500);text('Your next chapter starts with a hello.',812,327,20,'#758399',420);text('Looking forward to working together.',812,365,20,'#758399',420); + rr(811,405,445,62,14,'#F4F7FC','#E2EAF5');rr(825,417,37,37,9,'#E0EAFF');text('↓',843,435,22,C.blue,600,'center');text('Project brief.pdf',880,437,17,C.ink,540);text('2.4 MB',1234,437,13,'#8593AD',400,'right'); + rr(810,496,113,44,12,C.blue);text('↩ Reply',866,518,17,'#FFF',500,'center');text('Forward ↗',950,518,17,'#8391A9',500);ctx.restore();ctx.restore(); +} +export function sceneInbox(t,global) { + background(global);chrome(1,2);reveal('Meet your new inbox.',102,205,85,t,.02);pill('SEND. RECEIVE. DONE.',1438,177,367); + const p=spring(t/.85);ctx.save();ctx.translate(105+(1-p)*-190,309+(1-p)*420);ctx.translate(710,293);ctx.rotate((1-p)*-.09+.004*Math.sin(t));ctx.scale(.98+.02*p,.98+.02*p);ctx.translate(-710,-293);inbox(t);ctx.restore(); + robot(1660,708+180*(1-ease((t-.25)/.7)),.59,global,2); + let arrive=ease((t-.8)/.55);ctx.save();ctx.globalAlpha=arrive*(1-smooth((t-2.9)/.5));ctx.translate(1270+(1-arrive)*480,282-(1-arrive)*120);ctx.rotate((1-arrive)*.25);shadow(.15,40,16);rr(0,0,393,77,19,'#FFF','#D5E2F5');clearShadow();ellipse(40,38,23,23,'#E7F8F0');check(40,38,22,'#1F9E71');text('You’ve got good company.',77,30,18,C.ink,600);text('New message · hello@mailflare.co',77,53,14,'#8797B3',420);ctx.restore(); + const sent=ease((t-2.15)/.5);ctx.save();ctx.globalAlpha=sent;rr(1200,820+(1-sent)*70,268,63,18,'#155BFF');text('Message sent',1263,852+(1-sent)*70,21,'#FFF',550);check(1234,852+(1-sent)*70,24,'#B0EDFF');ctx.restore(); + reveal('Your domain. Every conversation.',107,962,28,t,.85,'#6C7D9B',450); + pill('ONE CLEAN WORKSPACE',1466,926,344); +} +export function teamCard(x,y,address,role,initial,color,t,delay,alias=false) { + let p=spring((t-delay)/.65);if(p<=0)return;ctx.save();ctx.translate(x+(x<800?-120:120)*(1-p),y+40*(1-p));ctx.scale(.93+.07*p,.93+.07*p);ctx.globalAlpha=clamp(p);shadow(.25,45,22);rr(0,0,440,153,25,gradient(0,0,440,153,['#1D3359','#142544']),'#435B83',1.5);clearShadow(); + ellipse(53,55,29,29,color);text(initial,53,55,23,'#FFF',650,'center');text(address,100,53,34,'#EDF6FF',580);text('mailflare.co',100,89,18,'#7E9FCF',450);rr(24,113,200,1,0,'#365075');text(role,27,134,15,'#97B5DD',500);ellipse(402,131,5,5,C.green);text(alias?'ALIAS':'ACTIVE',388,132,11,'#7CD3B0',600,'right');ctx.restore(); +} +export function wire(x,y,x2,y2,t,delay) { + let p=ease((t-delay)/.75);ctx.save();ctx.globalAlpha=p;ctx.beginPath();ctx.moveTo(x,y);ctx.bezierCurveTo((x+x2)/2,y,(x+x2)/2,y2,x2,y2);ctx.strokeStyle='#3D67A8';ctx.lineWidth=2;ctx.stroke(); + const q=(t*.55+delay)%1;let xx=(1-q)**3*x+3*(1-q)**2*q*(x+x2)/2+3*(1-q)*q*q*(x+x2)/2+q**3*x2,yy=(1-q)**3*y+3*(1-q)**2*q*y+3*(1-q)*q*q*y2+q**3*y2; + ctx.shadowColor='#71DDFF';ctx.shadowBlur=18;ellipse(xx,yy,5,5,'#91E6FF');ctx.restore(); +} +export function sceneTeam(t,global) { + background(global,true);chrome(1,3,true);reveal('One domain. Your whole team.',960,213,80,t,.03,'#FFF',610,'center'); + reveal('Built for individuals, developers, startups, and teams.',960,287,26,t,.18,'#8CA9D5',450,'center'); + wire(590,434,745,480,t,.2);wire(590,665,745,490,t,.42);wire(1330,434,1175,480,t,.35);wire(1330,665,1175,490,t,.57); + const p=spring((t-.13)/.7);ctx.save();ctx.translate(960,482);ctx.scale(p,p);shadow(.45,75,20);rr(-215,-54,430,108,27,gradient(-215,-54,215,54,['#3277FF','#0E48D6']),'#669FFF',2);clearShadow();envelope(-157,0,52);text('mailflare.co',-108,1,39,'#FFF',580);check(175,0,23,'#99EDCC');ctx.restore(); + teamCard(149,357,'hello@','Primary mailbox','H','#4085DF',t,.15);teamCard(149,588,'support@','Routes to your support team','S','#8D70DE',t,.43,true); + teamCard(1330,357,'hieu@','Owner · full access','H','#269889',t,.29);teamCard(1330,588,'team@','Shared mailbox','T','#CB8755',t,.57); + robot(960,743,.52,global,t>2?1:2,true); + const items=['Domains','Users','Aliases','Permissions'];for(let i=0;i<4;i++){let a=ease((t-.8-i*.14)/.5);ctx.save();ctx.globalAlpha=a;pill(items[i],545+i*211,911+(1-a)*35,194,true,true);ctx.restore();} + reveal('Simple setup. No mail-server maze.',960,995,21,t,1.85,'#B6D1F9',450,'center'); + spark(1222,684,17,'#63C7FF',t*.35);spark(705,365,12,'#63C7FF',t*-.45); +} +export function sceneHero(t,global) { + background(global,false,true);chrome(1,4,true); + ctx.save();ctx.globalAlpha=.11;ctx.strokeStyle='#D7EEFF';ctx.lineWidth=1.5;for(let i=0;i<4;i++){ctx.beginPath();ctx.ellipse(950,540,450+i*185,270+i*115,-.18,0,Math.PI*2);ctx.stroke();}ctx.restore(); + let phraseOut=1-smooth((t-1.2)/.45);ctx.save();ctx.globalAlpha=phraseOut;reveal('Your domain.',960,427,129,t,.04,'#FFF',630,'center');reveal('Your email.',960,570,129,t,.22,'#A8D8FF',630,'center');ctx.restore(); + let p=spring((t-1.22)/.9);if(p>0){ctx.save();ctx.globalAlpha=clamp(p);ctx.translate(930,473+(1-p)*70);ctx.scale(.88+.12*p,.88+.12*p);shadow(.22,60,20);rr(-483,-83,163,163,42,'#FFF');clearShadow();envelope(-402,1,121);text('Mailflare',-273,4,152,'#FFF',660);ctx.restore();} + reveal('Professional email for individuals and teams.',960,652,37,t,1.52,'#D8E8FF',450,'center'); + let url=ease((t-1.75)/.55);ctx.save();ctx.globalAlpha=url;shadow(.1,40,15);rr(788,750+(1-url)*40,344,81,40,'#FFFFFF');clearShadow();text('mailflare.co',947,791+(1-url)*40,30,C.blue,600,'center');arrow(1090,791+(1-url)*40,23);ctx.restore(); + const rp=ease((t-1.58)/.75);robot(1655+350*(1-rp),750,.51,global,1,true); + for(let i=0;i<6;i++){let a=i*Math.PI/3+global*.16;let dist=340+80*i/6;let xx=960+Math.cos(a)*dist*1.55,yy=515+Math.sin(a)*dist;ctx.save();ctx.globalAlpha=.2*clamp(p);spark(xx,yy,8+i%3*3,'#DDF4FF',a);ctx.restore();} + reveal('YOUR BUSINESS. BEAUTIFULLY ADDRESSED.',960,926,18,t,2.1,'#A5CBFF',550,'center'); +} +export function transition(t,at) { + const u=(t-at)/.52;if(u<0||u>1)return; + ctx.save();ctx.translate(lerp(-2500,2600,smooth(u)),0);ctx.transform(1,0,-.3,1,0,0); + const g=gradient(0,0,1100,0,['#2B79FF00','#8BE3FF','#FFFFFF','#195BFF','#175BFF00']);ctx.fillStyle=g;ctx.fillRect(0,-40,1100,H+80);ctx.restore(); +} +export function frame(t) { + ctx.resetTransform();ctx.globalAlpha=1;ctx.filter='none';clearShadow(); + if(t<3)sceneIntro(t);else if(t<7)sceneInbox(t-3,t);else if(t<11)sceneTeam(t-7,t);else sceneHero(t-11,t); + transition(t,2.75);transition(t,6.75);transition(t,10.75); + const g=ctx.createRadialGradient(960,520,450,960,520,1170);g.addColorStop(0,'#08142F00');g.addColorStop(1,'#08142F13');ctx.fillStyle=g;ctx.fillRect(0,0,W,H); +} +export async function render() { + const out=join(ROOT,'picture.mp4'); + const encoder=spawn('ffmpeg',['-y','-loglevel','error','-f','rawvideo','-pixel_format','rgba','-video_size',`${W}x${H}`,'-framerate',`${FPS}`,'-i','pipe:0','-an','-c:v','libx264','-preset','fast','-crf','17','-pix_fmt','yuv420p','-movflags','+faststart',out],{stdio:['pipe','inherit','inherit']}); + const sample=createCanvas(W,H),sc=sample.getContext('2d'); + for(let n=0;n<15*FPS;n++){ + const t=n/FPS;frame(t); + if([120,300,540,840].includes(n))writeFileSync(join(ROOT,`scene-${n}.png`),canvas.toBuffer('image/png')); + if(n===840)writeFileSync(join(ROOT,'poster.png'),canvas.toBuffer('image/png')); + // Two shutter samples keep fast entrances and whip transitions soft at 60 fps. + sc.globalAlpha=1;sc.drawImage(canvas,0,0);frame(t+1/FPS*.38);sc.globalAlpha=.42;sc.drawImage(canvas,0,0); + if(!encoder.stdin.write(Buffer.from(sc.getImageData(0,0,W,H).data)))await once(encoder.stdin,'drain'); + if(n%120===0)process.stdout.write(`Rendered ${n}/${15*FPS} frames\n`); + } + encoder.stdin.end();const [code]=await once(encoder,'close');if(code!==0)throw new Error(`Encoder exited ${code}`);process.stdout.write('Picture render complete.\n'); +} diff --git a/media/launch-film/voice-1.json b/media/launch-film/voice-1.json new file mode 100644 index 0000000..58de216 --- /dev/null +++ b/media/launch-film/voice-1.json @@ -0,0 +1 @@ +{"text": "Your business deserves a better address.", "alignment": {"characters": [" ", "Y", "o", "u", "r", " ", "b", "u", "s", "i", "n", "e", "s", "s", " ", "d", "e", "s", "e", "r", "v", "e", "s", " ", "a", " ", "b", "e", "t", "t", "e", "r", " ", "a", "d", "d", "r", "e", "s", "s", ".", " "], "character_start_times_seconds": [0.0, 0.046, 0.104, 0.139, 0.186, 0.244, 0.313, 0.36, 0.43, 0.488, 0.522, 0.557, 0.592, 0.639, 0.685, 0.731, 0.766, 0.836, 0.894, 0.952, 0.998, 1.033, 1.08, 1.115, 1.149, 1.173, 1.231, 1.265, 1.3, 1.347, 1.393, 1.428, 1.463, 1.498, 1.533, 1.579, 1.625, 1.695, 1.765, 1.823, 1.927, 2.043], "character_end_times_seconds": [0.046, 0.104, 0.139, 0.186, 0.244, 0.313, 0.36, 0.43, 0.488, 0.522, 0.557, 0.592, 0.639, 0.685, 0.731, 0.766, 0.836, 0.894, 0.952, 0.998, 1.033, 1.08, 1.115, 1.149, 1.173, 1.231, 1.265, 1.3, 1.347, 1.393, 1.428, 1.463, 1.498, 1.533, 1.579, 1.625, 1.695, 1.765, 1.823, 1.927, 2.043, 2.276]}} \ No newline at end of file diff --git a/media/launch-film/voice-1.mp3 b/media/launch-film/voice-1.mp3 new file mode 100644 index 0000000..30bcd4d Binary files /dev/null and b/media/launch-film/voice-1.mp3 differ diff --git a/media/launch-film/voice-2.json b/media/launch-film/voice-2.json new file mode 100644 index 0000000..5c09edc --- /dev/null +++ b/media/launch-film/voice-2.json @@ -0,0 +1 @@ +{"text": "Mailflare gives you professional email on your own domain.", "alignment": {"characters": [" ", "M", "a", "i", "l", "f", "l", "a", "r", "e", " ", "g", "i", "v", "e", "s", " ", "y", "o", "u", " ", "p", "r", "o", "f", "e", "s", "s", "i", "o", "n", "a", "l", " ", "e", "m", "a", "i", "l", " ", "o", "n", " ", "y", "o", "u", "r", " ", "o", "w", "n", " ", "d", "o", "m", "a", "i", "n", ".", " "], "character_start_times_seconds": [0.0, 0.046, 0.104, 0.163, 0.209, 0.29, 0.36, 0.418, 0.488, 0.534, 0.557, 0.592, 0.627, 0.662, 0.685, 0.72, 0.743, 0.766, 0.789, 0.813, 0.836, 0.894, 0.929, 0.964, 1.01, 1.068, 1.126, 1.173, 1.207, 1.242, 1.265, 1.3, 1.335, 1.37, 1.44, 1.544, 1.614, 1.672, 1.718, 1.765, 1.834, 1.881, 1.916, 1.95, 1.974, 1.997, 2.02, 2.055, 2.09, 2.113, 2.148, 2.194, 2.229, 2.264, 2.334, 2.38, 2.426, 2.485, 2.566, 2.635], "character_end_times_seconds": [0.046, 0.104, 0.163, 0.209, 0.29, 0.36, 0.418, 0.488, 0.534, 0.557, 0.592, 0.627, 0.662, 0.685, 0.72, 0.743, 0.766, 0.789, 0.813, 0.836, 0.894, 0.929, 0.964, 1.01, 1.068, 1.126, 1.173, 1.207, 1.242, 1.265, 1.3, 1.335, 1.37, 1.44, 1.544, 1.614, 1.672, 1.718, 1.765, 1.834, 1.881, 1.916, 1.95, 1.974, 1.997, 2.02, 2.055, 2.09, 2.113, 2.148, 2.194, 2.229, 2.264, 2.334, 2.38, 2.426, 2.485, 2.566, 2.635, 2.879]}} \ No newline at end of file diff --git a/media/launch-film/voice-2.mp3 b/media/launch-film/voice-2.mp3 new file mode 100644 index 0000000..2cb1678 Binary files /dev/null and b/media/launch-film/voice-2.mp3 differ diff --git a/media/launch-film/voice-3.json b/media/launch-film/voice-3.json new file mode 100644 index 0000000..c84875b --- /dev/null +++ b/media/launch-film/voice-3.json @@ -0,0 +1 @@ +{"text": "Mailboxes, aliases, your whole team. One place.", "alignment": {"characters": [" ", "M", "a", "i", "l", "b", "o", "x", "e", "s", ",", " ", "a", "l", "i", "a", "s", "e", "s", ",", " ", "y", "o", "u", "r", " ", "w", "h", "o", "l", "e", " ", "t", "e", "a", "m", ".", " ", "O", "n", "e", " ", "p", "l", "a", "c", "e", ".", " "], "character_start_times_seconds": [0.0, 0.058, 0.104, 0.151, 0.209, 0.279, 0.348, 0.453, 0.522, 0.627, 0.662, 0.697, 0.731, 0.836, 0.906, 0.975, 1.08, 1.126, 1.231, 1.265, 1.3, 1.335, 1.37, 1.405, 1.451, 1.498, 1.556, 1.591, 1.637, 1.695, 1.741, 1.776, 1.811, 1.858, 1.927, 1.985, 2.055, 2.125, 2.229, 2.299, 2.368, 2.403, 2.438, 2.485, 2.543, 2.612, 2.67, 2.74, 2.798], "character_end_times_seconds": [0.058, 0.104, 0.151, 0.209, 0.279, 0.348, 0.453, 0.522, 0.627, 0.662, 0.697, 0.731, 0.836, 0.906, 0.975, 1.08, 1.126, 1.231, 1.265, 1.3, 1.335, 1.37, 1.405, 1.451, 1.498, 1.556, 1.591, 1.637, 1.695, 1.741, 1.776, 1.811, 1.858, 1.927, 1.985, 2.055, 2.125, 2.229, 2.299, 2.368, 2.403, 2.438, 2.485, 2.543, 2.612, 2.67, 2.74, 2.798, 3.019]}} \ No newline at end of file diff --git a/media/launch-film/voice-3.mp3 b/media/launch-film/voice-3.mp3 new file mode 100644 index 0000000..a6f499f Binary files /dev/null and b/media/launch-film/voice-3.mp3 differ diff --git a/media/launch-film/voice-4.json b/media/launch-film/voice-4.json new file mode 100644 index 0000000..145d699 --- /dev/null +++ b/media/launch-film/voice-4.json @@ -0,0 +1 @@ +{"text": "Your domain. Your email. Mailflare.", "alignment": {"characters": [" ", "Y", "o", "u", "r", " ", "d", "o", "m", "a", "i", "n", ".", " ", "Y", "o", "u", "r", " ", "e", "m", "a", "i", "l", ".", " ", "M", "a", "i", "l", "f", "l", "a", "r", "e", ".", " "], "character_start_times_seconds": [0.0, 0.058, 0.093, 0.128, 0.163, 0.197, 0.244, 0.279, 0.337, 0.383, 0.43, 0.488, 0.522, 0.546, 0.58, 0.604, 0.627, 0.662, 0.697, 0.743, 0.813, 0.882, 0.929, 0.975, 1.045, 1.115, 1.254, 1.312, 1.358, 1.416, 1.486, 1.556, 1.625, 1.707, 1.741, 1.811, 1.869], "character_end_times_seconds": [0.058, 0.093, 0.128, 0.163, 0.197, 0.244, 0.279, 0.337, 0.383, 0.43, 0.488, 0.522, 0.546, 0.58, 0.604, 0.627, 0.662, 0.697, 0.743, 0.813, 0.882, 0.929, 0.975, 1.045, 1.115, 1.254, 1.312, 1.358, 1.416, 1.486, 1.556, 1.625, 1.707, 1.741, 1.811, 1.869, 2.09]}} \ No newline at end of file diff --git a/media/launch-film/voice-4.mp3 b/media/launch-film/voice-4.mp3 new file mode 100644 index 0000000..6149db5 Binary files /dev/null and b/media/launch-film/voice-4.mp3 differ diff --git a/media/mailflare-motion.mp4 b/media/mailflare-motion.mp4 new file mode 100644 index 0000000..1158c88 Binary files /dev/null and b/media/mailflare-motion.mp4 differ diff --git a/media/mailflare-showreel.mp4 b/media/mailflare-showreel.mp4 new file mode 100644 index 0000000..7da901d Binary files /dev/null and b/media/mailflare-showreel.mp4 differ diff --git a/media/render.py b/media/render.py new file mode 100644 index 0000000..b9f7bf1 --- /dev/null +++ b/media/render.py @@ -0,0 +1,4 @@ +from video_utils import render_video + + +render_video() diff --git a/media/showreel/assets/character-a.png b/media/showreel/assets/character-a.png new file mode 100644 index 0000000..32b4f55 Binary files /dev/null and b/media/showreel/assets/character-a.png differ diff --git a/media/showreel/assets/character-b.png b/media/showreel/assets/character-b.png new file mode 100644 index 0000000..cac808f Binary files /dev/null and b/media/showreel/assets/character-b.png differ diff --git a/media/showreel/assets/character-c.png b/media/showreel/assets/character-c.png new file mode 100644 index 0000000..dc3b86d Binary files /dev/null and b/media/showreel/assets/character-c.png differ diff --git a/media/showreel/assets/icon.png b/media/showreel/assets/icon.png new file mode 100644 index 0000000..63e95e5 Binary files /dev/null and b/media/showreel/assets/icon.png differ diff --git a/media/showreel/film.html b/media/showreel/film.html new file mode 100644 index 0000000..36d62b5 --- /dev/null +++ b/media/showreel/film.html @@ -0,0 +1,669 @@ + + + + +Mailflare showreel + + + +
+
+
+
+
+
+
+
+ + + + +
+
Mailflare
+
+
+
+
+
+
+
+
+ +
+
+ + + + mailflare · inbox + hello@mailflare.co +
+
+
+
+  Compose
+ +
+
+
+ +
+
+
+
Live in minutes · no mail server
+ +
+ +
mailflare.co
Custom domain
+
+
+
+
+
+
New message · hello@mailflare.co
+
+
+ +
+
Your domain.
+
Your email.
+
+ +

Mailflare

+
Professional email for individuals and teams.
+
mailflare.co
+
+
+
+
+
+
+ + + diff --git a/media/showreel/render.mjs b/media/showreel/render.mjs new file mode 100644 index 0000000..7ae17ba --- /dev/null +++ b/media/showreel/render.mjs @@ -0,0 +1,47 @@ +import { createRequire } from "node:module"; +import { mkdirSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const require = createRequire("/tmp/showreel-render/package.json"); +const puppeteer = require("puppeteer-core"); + +const dir = path.dirname(fileURLToPath(import.meta.url)); +const html = path.join(dir, "film.html"); +const chrome = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; + +const arg = process.argv.find((a) => a.startsWith("--times=")); +const times = arg + ? arg.slice("--times=".length).split(",").map(Number) + : null; + +const outDir = process.argv.find((a) => a.startsWith("--out="))?.slice("--out=".length) + || "/tmp/showreel-frames"; +mkdirSync(outDir, { recursive: true }); + +const browser = await puppeteer.launch({ + executablePath: chrome, + headless: "new", + args: ["--hide-scrollbars", "--disable-lcd-text", "--force-color-profile=srgb"], +}); +const page = await browser.newPage(); +await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1 }); +await page.goto("file://" + html, { waitUntil: "load" }); +await page.evaluate(() => document.fonts.ready); + +const frames = times ?? Array.from({ length: 15 * 30 }, (_, i) => i / 30); +for (let i = 0; i < frames.length; i++) { + const t = frames[i]; + await page.evaluate((time) => window.seek(time), t); + const name = times + ? `proof-${String(t).replace(".", "_")}.jpg` + : `f${String(i).padStart(4, "0")}.jpg`; + await page.screenshot({ + path: path.join(outDir, name), + type: "jpeg", + quality: 92, + }); + if (!times && i % 30 === 0) process.stdout.write(`t=${t.toFixed(2)}\n`); +} +await browser.close(); +console.log(outDir); diff --git a/media/video_utils.py b/media/video_utils.py new file mode 100644 index 0000000..f406327 --- /dev/null +++ b/media/video_utils.py @@ -0,0 +1,386 @@ +import math +import os +import struct +import subprocess +import tempfile +import wave +from pathlib import Path + +from PIL import Image, ImageDraw, ImageFilter, ImageFont + + +W, H, FPS, DURATION = 1280, 720, 24, 15 +ROOT = Path(__file__).resolve().parent.parent +OUT = Path(__file__).resolve().parent / "mailflare-motion.mp4" +BLUE = (38, 112, 246) +NAVY = (9, 25, 55) +INK = (17, 34, 62) +PALE = (244, 248, 255) +FONT = "/System/Library/Fonts/Supplemental/Arial.ttf" +BOLD = "/System/Library/Fonts/Supplemental/Arial Bold.ttf" + + +def f(size, bold=False): + return ImageFont.truetype(BOLD if bold else FONT, size) + + +def clamp(x): + return max(0.0, min(1.0, x)) + + +def ease(x): + x = clamp(x) + return 1 - (1 - x) ** 3 + + +def smooth(x): + x = clamp(x) + return x * x * (3 - 2 * x) + + +def phase(t, start, end): + return ease((t - start) / (end - start)) + + +def alpha(t, start, end, fade=0.28): + return smooth((t - start) / fade) * (1 - smooth((t - end + fade) / fade)) + + +def mix(a, b, p): + return a + (b - a) * p + + +def rgba(color, a=255): + return (*color, int(255 * clamp(a))) + + +def text(draw, xy, message, size, color, bold=False, anchor=None, spacing=0): + draw.text(xy, message, font=f(size, bold), fill=color, anchor=anchor, spacing=spacing) + + +def round_box(draw, box, radius, fill, outline=None, width=1): + draw.rounded_rectangle(tuple(int(v) for v in box), radius=int(radius), fill=fill, outline=outline, width=width) + + +def shadow_card(size, radius=28, blur=28, opacity=52): + layer = Image.new("RGBA", (size[0] + blur * 4, size[1] + blur * 4)) + d = ImageDraw.Draw(layer) + b = blur * 2 + d.rounded_rectangle((b, b, b + size[0], b + size[1]), radius, fill=(15, 42, 90, opacity)) + return layer.filter(ImageFilter.GaussianBlur(blur)) + + +def glow(img, xy, radius, color, strength=130): + layer = Image.new("RGBA", (W, H)) + d = ImageDraw.Draw(layer) + x, y = xy + d.ellipse((x - radius, y - radius, x + radius, y + radius), fill=(*color, strength)) + img.alpha_composite(layer.filter(ImageFilter.GaussianBlur(radius * 0.85))) + + +def envelope(draw, cx, cy, scale, color=BLUE, open_amount=0): + w, h = 88 * scale, 60 * scale + x, y = cx - w / 2, cy - h / 2 + draw.rounded_rectangle((x, y, x + w, y + h), 9 * scale, fill=color) + draw.polygon([(x + 4 * scale, y + 5 * scale), (cx, y + 34 * scale), (x + w - 4 * scale, y + 5 * scale)], fill=(221, 244, 255)) + draw.polygon([(x + 4 * scale, y + h - 5 * scale), (cx, y + 29 * scale), (x + w - 4 * scale, y + h - 5 * scale)], fill=(15, 167, 224)) + if open_amount: + draw.polygon([(x + 4 * scale, y + 5 * scale), (cx, y - 23 * scale * open_amount), (x + w - 4 * scale, y + 5 * scale)], fill=(239, 252, 255)) + + +def base_dark(): + im = Image.new("RGBA", (W, H)) + d = ImageDraw.Draw(im) + for y in range(H): + p = y / H + d.line((0, y, W, y), fill=(int(8 + p * 3), int(20 + p * 17), int(49 + p * 29), 255)) + return im + + +def base_light(): + im = Image.new("RGBA", (W, H)) + d = ImageDraw.Draw(im) + for y in range(H): + p = y / H + d.line((0, y, W, y), fill=(int(248 - 6 * p), int(251 - 5 * p), 255, 255)) + return im + + +def draw_brand(draw, x, y, size=28, dark=False): + envelope(draw, x + size / 2, y + size / 2, size / 88) + text(draw, (x + size + 12, y + size / 2), "mailflare", int(size * 0.72), (255, 255, 255) if dark else INK, True, "lm") + + +def draw_ui(t): + im = Image.new("RGBA", (936, 532), (255, 255, 255, 255)) + d = ImageDraw.Draw(im) + round_box(d, (0, 0, 935, 531), 24, (255, 255, 255), (219, 230, 246), 2) + round_box(d, (0, 0, 935, 53), 24, (247, 250, 255)) + d.rectangle((0, 30, 935, 53), fill=(247, 250, 255)) + d.line((0, 53, 935, 53), fill=(229, 235, 245), width=1) + for i, c in enumerate(((255, 111, 111), (255, 192, 86), (66, 207, 128))): + d.ellipse((21 + i * 18, 20, 30 + i * 18, 29), fill=c) + text(d, (468, 28), "mailflare / inbox", 14, (117, 133, 159), True, "mm") + d.rectangle((0, 54, 187, 531), fill=(247, 249, 254)) + draw_brand(d, 19, 75, 25) + round_box(d, (20, 126, 163, 169), 18, (220, 234, 255)) + text(d, (48, 147), "+ Compose", 15, (29, 81, 161), True, "lm") + nav = [("Inbox", "18"), ("Starred", ""), ("Snoozed", ""), ("Sent", ""), ("Drafts", "4")] + for i, (label, count) in enumerate(nav): + yy = 196 + i * 46 + if i == 0: + round_box(d, (12, yy - 17, 176, yy + 19), 15, (225, 237, 255)) + d.ellipse((30, yy - 4, 38, yy + 4), fill=BLUE if i == 0 else (157, 169, 189)) + text(d, (50, yy), label, 15, (26, 52, 92) if i == 0 else (107, 121, 145), i == 0, "lm") + if count: + text(d, (151, yy), count, 13, BLUE, True, "mm") + round_box(d, (211, 75, 902, 122), 22, (238, 244, 253)) + d.ellipse((232, 90, 248, 106), outline=(107, 132, 170), width=2) + d.line((246, 104, 253, 111), fill=(107, 132, 170), width=2) + text(d, (266, 98), "Search mail", 15, (132, 149, 174), False, "lm") + text(d, (221, 153), "Priority inbox", 24, INK, True, "lm") + text(d, (878, 153), "1–4 of 18", 12, (132, 149, 174), False, "rm") + messages = [ + ("M", "Maya Chen", "Product launch tomorrow", "Can we share the final update?", "9:41 AM"), + ("N", "Northline", "Your domain is connected", "Mail is ready for hello@northline.dev", "8:52 AM"), + ("T", "Theo Park", "One more thing", "Thanks for the thoughtful note!", "Yesterday"), + ("S", "Studio Notes", "A fresh start for Monday", "Ideas worth opening.", "Mon"), + ] + for i, (initial, sender, subject, preview, time) in enumerate(messages): + yy = 188 + i * 82 + if i == 0 and t > 3.65: + round_box(d, (201, yy - 1, 925, yy + 79), 14, (237, 245, 255)) + d.line((203, yy + 79, 925, yy + 79), fill=(236, 240, 247)) + d.ellipse((221, yy + 14, 259, yy + 52), fill=[(124, 157, 239), (75, 199, 221), (232, 172, 111), (174, 145, 230)][i]) + text(d, (240, yy + 33), initial, 15, (255, 255, 255), True, "mm") + text(d, (273, yy + 15), sender, 16, INK, True) + text(d, (273, yy + 39), subject, 14, (50, 65, 90), True) + text(d, (273, yy + 60), preview, 12, (143, 156, 177)) + text(d, (882, yy + 17), time, 12, (130, 145, 166), False, "ra") + if i == 0: + d.ellipse((903, yy + 43, 912, yy + 52), fill=BLUE) + return im + + +def feature_card(im, box, title, sub, symbol, amount): + x, y, w, h = box + lift = 24 * (1 - ease(amount)) + y += lift + d = ImageDraw.Draw(im) + round_box(d, (x, y, x + w, y + h), 26, rgba((255, 255, 255), amount), rgba((161, 196, 251), amount * 0.45), 2) + round_box(d, (x + 20, y + 20, x + 68, y + 68), 14, rgba((222, 237, 255), amount)) + text(d, (x + 44, y + 45), symbol, 25, rgba(BLUE, amount), True, "mm") + text(d, (x + 20, y + 94), title, 19, rgba(INK, amount), True) + text(d, (x + 20, y + 124), sub, 13, rgba((113, 135, 166), amount)) + + +def render_frame(t, dark, light, ui, logo): + if t < 2.65: + im = dark.copy() + d = ImageDraw.Draw(im) + glow(im, (920, 210), 240, (33, 105, 240), 100) + glow(im, (180, 660), 180, (15, 184, 219), 65) + d = ImageDraw.Draw(im) + for i in range(18): + x = (i * 139 + t * (34 + i % 4 * 13)) % (W + 100) - 50 + y = (i * 107 + math.sin(t * 1.8 + i) * 13) % H + d.ellipse((x, y, x + 2, y + 2), fill=(98, 166, 251, 80)) + p = phase(t, 0.08, 0.72) + x = mix(1400, 880, p) + y = mix(-90, 335, p) + math.sin(t * 3.3) * 7 + d.ellipse((x - 152, y - 152, x + 152, y + 152), outline=(65, 141, 241, 95), width=2) + d.ellipse((x - 112, y - 112, x + 112, y + 112), outline=(65, 141, 241, 75), width=2) + envelope(d, x, y, 2.25, (28, 146, 232), phase(t, 0.65, 1.15)) + for k in range(3): + xx = x + 170 + k * 35 + (1 - p) * 180 + d.line((xx, y - 30 + k * 30, xx + 75, y - 30 + k * 30), fill=(39, 141, 252, int(140 * p)), width=3) + text(d, (85 - 60 * (1 - phase(t, 0.1, 0.5)), 128), "INTRODUCING MAILFLARE", 17, (108, 185, 255), True) + reveal = int(len("Email, with momentum.") * phase(t, 0.25, 1.48)) + text(d, (80, 204), "Email, with", 69, (255, 255, 255), True) + text(d, (80, 292), "momentum."[:max(0, reveal - 12)], 74, (91, 177, 255), True) + line_p = phase(t, 1.23, 1.8) + d.rounded_rectangle((83, 403, 83 + 550 * line_p, 408), radius=2, fill=(79, 173, 254)) + text(d, (85, 450), "A familiar inbox. A more capable workflow.", 24, (174, 199, 230)) + if t > 2.25: + im = crossfade(im, light, phase(t, 2.25, 2.65)) + return im + + if t < 5.65: + im = light.copy() + d = ImageDraw.Draw(im) + draw_brand(d, 57, 36, 33) + text(d, (1222, 56), "THE INBOX", 13, BLUE, True, "rm") + text(d, (640, 114), "Feels like home.", 48, INK, True, "mm") + text(d, (640, 160), "Everything in its place. Every message in reach.", 20, (109, 133, 164), False, "mm") + p = phase(t, 2.66, 3.25) + s = mix(0.89, 1.0, p) + panel = ui.resize((int(ui.width * s), int(ui.height * s)), Image.Resampling.LANCZOS) + px = int((W - panel.width) / 2) + py = int(206 + 72 * (1 - p)) + sh = shadow_card((panel.width, panel.height), 25, 25, 50) + im.alpha_composite(sh, (px - 50, py - 43)) + im.alpha_composite(panel, (px, py)) + d = ImageDraw.Draw(im) + if t > 4.35: + q = phase(t, 4.35, 4.85) + x = 945 + 180 * (1 - q) + y = 548 + round_box(d, (x, y, x + 264, y + 71), 20, (37, 112, 246)) + text(d, (x + 22, y + 24), "✦ Mail, organized", 19, (255, 255, 255), True) + text(d, (x + 22, y + 49), "Search · stars · snooze", 12, (219, 235, 255)) + return im + + if t < 9.25: + im = light.copy() + d = ImageDraw.Draw(im) + draw_brand(d, 57, 36, 33) + text(d, (1222, 56), "PEOPLE + AGENTS", 13, BLUE, True, "rm") + text(d, (640, 116), "Human touch. Agent speed.", 47, INK, True, "mm") + text(d, (640, 158), "A thoughtful reply starts here.", 19, (108, 133, 164), False, "mm") + p = phase(t, 5.7, 6.22) + y = 226 + 50 * (1 - p) + sh = shadow_card((475, 350), 30, 26, 43) + im.alpha_composite(sh, (55, int(y) - 45)) + round_box(d, (110, y, 585, y + 350), 29, (255, 255, 255), (224, 233, 246), 2) + d.ellipse((138, y + 31, 184, y + 77), fill=(122, 155, 239)) + text(d, (161, y + 54), "M", 19, (255, 255, 255), True, "mm") + text(d, (198, y + 32), "Maya Chen", 19, INK, True) + text(d, (198, y + 57), "To: hello@northline.dev", 13, (132, 151, 178)) + d.line((139, y + 97, 551, y + 97), fill=(228, 235, 245), width=2) + text(d, (140, y + 128), "Product launch tomorrow", 23, INK, True) + text(d, (140, y + 179), "Hi team, can we share the final", 17, (76, 96, 126)) + text(d, (140, y + 208), "update with everyone tomorrow?", 17, (76, 96, 126)) + text(d, (140, y + 251), "Thanks, Maya", 17, (76, 96, 126)) + d.line((570, y + 170, 685, y + 170), fill=(66, 142, 252), width=4) + d.polygon([(685, y + 170), (674, y + 162), (674, y + 178)], fill=(66, 142, 252)) + ap = phase(t, 6.45, 7.0) + ax = 695 + 90 * (1 - ap) + round_box(d, (ax, y, ax + 475, y + 350), 29, (255, 255, 255), (207, 225, 249), 2) + round_box(d, (ax + 24, y + 24, ax + 78, y + 78), 16, (225, 237, 255)) + text(d, (ax + 51, y + 51), "✦", 28, BLUE, True, "mm") + text(d, (ax + 93, y + 33), "Mailflare assistant", 18, INK, True) + text(d, (ax + 93, y + 59), "Working with your inbox", 13, (126, 147, 176)) + d.line((ax + 24, y + 99, ax + 450, y + 99), fill=(230, 238, 249), width=2) + text(d, (ax + 27, y + 125), "Draft a warm reply to Maya", 18, (53, 78, 113), True) + q = phase(t, 7.06, 7.62) + round_box(d, (ax + 24, y + 168, ax + 450, y + 276), 18, (239, 246, 255)) + text(d, (ax + 45, y + 191), "DRAFT READY", 12, BLUE, True) + text(d, (ax + 45, y + 221), "Absolutely, Maya — the update", 17, INK) + text(d, (ax + 45, y + 247), "is ready to share tomorrow…", 17, INK) + if q > 0: + round_box(d, (ax + 24, y + 292, ax + 249, y + 329), 17, rgba(BLUE, q)) + text(d, (ax + 136, y + 311), "Review to send →", 15, rgba((255, 255, 255), q), True, "mm") + if t > 8.08: + ring = (t - 8.08) * 80 + d.ellipse((ax + 136 - ring, y + 311 - ring, ax + 136 + ring, y + 311 + ring), outline=(38, 112, 246, max(0, int(145 - ring * 2))), width=3) + text(d, (640, 643), "The agent prepares. You approve.", 20, (84, 111, 148), True, "mm") + if t > 8.95: + im = crossfade(im, dark, phase(t, 8.95, 9.25)) + return im + + if t < 12.3: + im = dark.copy() + glow(im, (662, 330), 210, (23, 99, 246), 72) + d = ImageDraw.Draw(im) + draw_brand(d, 57, 36, 33, True) + text(d, (1222, 56), "YOUR WORKFLOW", 13, (107, 179, 255), True, "rm") + text(d, (640, 130), "One home for every address.", 47, (255, 255, 255), True, "mm") + text(d, (640, 177), "Built around your domain. Ready for your tools.", 19, (164, 191, 226), False, "mm") + pp = [phase(t, 9.5 + i * 0.17, 10.07 + i * 0.17) for i in range(4)] + positions = [(114, 280), (383, 318), (652, 280), (921, 318)] + cards = [ + ("Custom domains", "Your identity, everywhere", "@"), + ("Smart routing", "Mail lands where it should", "↗"), + ("API + webhooks", "Connect your stack", "⌘"), + ("Agent access", "MCP-ready workflows", "✦"), + ] + for i, (x, y) in enumerate(positions): + feature_card(im, (x, y, 245, 205), *cards[i], pp[i]) + d = ImageDraw.Draw(im) + p = phase(t, 11.35, 11.8) + round_box(d, (418, 599, 862, 645), 22, rgba((30, 75, 139), p)) + text(d, (640, 622), "Self-hosted • Cloudflare-powered", 17, rgba((194, 223, 252), p), True, "mm") + if t > 12.0: + blue = Image.new("RGBA", (W, H), (29, 103, 236, 255)) + im = crossfade(im, blue, phase(t, 12.0, 12.3)) + return im + + im = Image.new("RGBA", (W, H), (29, 103, 236, 255)) + glow(im, (640, 330), 245, (90, 184, 255), 50) + d = ImageDraw.Draw(im) + for i in range(28): + a = i * 2 * math.pi / 28 + t * 0.18 + r = 320 + 13 * math.sin(t * 2 + i) + x = 640 + math.cos(a) * r + y = 333 + math.sin(a) * r + d.ellipse((x - 2, y - 2, x + 2, y + 2), fill=(135, 202, 255, 92)) + p = phase(t, 12.42, 13.03) + scale = mix(0.72, 1.0, p) + 0.018 * math.sin(t * 3) + icon = logo.resize((int(120 * scale), int(120 * scale)), Image.Resampling.LANCZOS) + im.alpha_composite(icon, (int(640 - icon.width / 2), int(187 - icon.height / 2))) + d = ImageDraw.Draw(im) + text(d, (640, 322 + 38 * (1 - p)), "mailflare", 81, (255, 255, 255), True, "mm") + q = phase(t, 13.1, 13.75) + text(d, (640, 416), "Email for humans + agents.", 27, rgba((225, 241, 255), q), False, "mm") + d.rounded_rectangle((640 - 88 * q, 470, 640 + 88 * q, 474), radius=2, fill=rgba((170, 227, 255), q)) + text(d, (640, 526), "YOUR MAIL. YOUR MOMENTUM.", 15, rgba((202, 231, 255), phase(t, 13.7, 14.25)), True, "mm") + return im + + +def crossfade(a, b, p): + return Image.blend(a, b, clamp(p)) + + +def make_audio(path): + rate = 44100 + beats = (0.14, 0.72, 1.34, 2.65, 3.45, 4.38, 5.68, 6.54, 7.45, 8.12, 9.26, 9.78, 10.26, 10.75, 11.25, 12.34, 13.15) + with wave.open(path, "wb") as wav: + wav.setnchannels(1) + wav.setsampwidth(2) + wav.setframerate(rate) + chunk = bytearray() + for i in range(rate * DURATION): + t = i / rate + section = 0.5 + 0.5 * math.sin(t * math.pi / DURATION) + pad = (math.sin(2 * math.pi * 110 * t) + 0.53 * math.sin(2 * math.pi * 164.81 * t) + 0.31 * math.sin(2 * math.pi * 220 * t)) * 0.035 * section + rhythm = 0.0 + for b in beats: + dt = t - b + if 0 <= dt < 0.28: + rhythm += math.sin(2 * math.pi * (108 - 55 * dt) * dt) * math.exp(-24 * dt) * 0.18 + if 0 <= dt < 0.10: + rhythm += math.sin(2 * math.pi * 920 * dt) * math.exp(-48 * dt) * 0.024 + shimmer = math.sin(2 * math.pi * 440 * t) * math.sin(2 * math.pi * 0.7 * t) * 0.009 + sample = max(-1, min(1, pad + rhythm + shimmer)) + chunk += struct.pack(" 65536: + wav.writeframes(chunk) + chunk.clear() + if chunk: + wav.writeframes(chunk) + + +def render_video(): + dark, light = base_dark(), base_light() + ui = draw_ui(4.0) + logo = Image.open(ROOT / "public/icon-96.png").convert("RGBA") + with tempfile.TemporaryDirectory() as tmp: + silent = os.path.join(tmp, "silent.mp4") + audio = os.path.join(tmp, "sound.wav") + encoder = subprocess.Popen([ + "ffmpeg", "-y", "-loglevel", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", + "-s", f"{W}x{H}", "-r", str(FPS), "-i", "-", "-an", "-c:v", "libx264", + "-preset", "medium", "-crf", "17", "-pix_fmt", "yuv420p", "-movflags", "+faststart", silent + ], stdin=subprocess.PIPE) + for n in range(FPS * DURATION): + frame = render_frame(n / FPS, dark, light, ui, logo).convert("RGB") + encoder.stdin.write(frame.tobytes()) + encoder.stdin.close() + encoder.wait() + make_audio(audio) + subprocess.run([ + "ffmpeg", "-y", "-loglevel", "error", "-i", silent, "-i", audio, + "-c:v", "copy", "-c:a", "aac", "-b:a", "192k", "-t", str(DURATION), + "-movflags", "+faststart", str(OUT) + ], check=True) + print(OUT) diff --git a/package-lock.json b/package-lock.json index 8db12d9..1ad1748 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,17 @@ { "name": "mailflare", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mailflare", - "version": "0.2.0", + "version": "0.2.1", "license": "SEE LICENSE IN LICENSE", "dependencies": { + "@ai-sdk/openai-compatible": "^3.0.55", + "@ai-sdk/react": "^4.0.116", + "@modelcontextprotocol/server": "^2.1.0", "@opennextjs/cloudflare": "^1.19.9", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -19,6 +22,7 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toast": "^1.2.15", "@tanstack/react-query": "^5.100.11", + "ai": "^7.0.113", "bcryptjs": "^3.0.3", "better-sqlite3": "^13.0.3", "class-variance-authority": "^0.7.1", @@ -34,8 +38,11 @@ "qrcode": "^1.5.4", "react": "^19.1.7", "react-dom": "^19.1.7", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "smtp-server": "^3.19.12", "tailwind-merge": "^3.6.0", + "workers-ai-provider": "^4.0.0", "zod": "^4.4.3" }, "devDependencies": { @@ -60,8 +67,112 @@ "wrangler": "^4.127.1" } }, + "node_modules/@ai-sdk/gateway": { + "version": "4.0.91", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.91.tgz", + "integrity": "sha512-bUjtlAh1wV8aiYIOuPK4mBA3oAd7cETU4A1oTXNCkjGagZJvFfj+hx4b1ZaF5gHL0IPa6KfmIvZ0VVKhAlapVg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.47", + "@vercel/oidc": "3.2.0" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/mcp": { + "version": "2.0.57", + "resolved": "https://registry.npmjs.org/@ai-sdk/mcp/-/mcp-2.0.57.tgz", + "integrity": "sha512-Ox7mjtaN7Yu4YHOjBFV6+ibeG8vKTwsu3EN6059Tj977uaxJwCLTzm/oSKnR99sQKYCzz86L7mgc9rt+7wtsNw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.47", + "cross-spawn": "^7.0.6", + "pkce-challenge": "^5.0.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai-compatible": { + "version": "3.0.55", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.55.tgz", + "integrity": "sha512-mM/+YepFSgd1som+WYm8OAUYrl0X6T9ix327/2vszLGrrql8K7ZwL3MQ7AIs0tYS6QEy3mhvqz4w9pBFz7UhSg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.47" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.18.tgz", + "integrity": "sha512-+GZJIgz1jk86pwEbb3f1BD2bdoSKyWE4Jg4YUc7NMnMozbWemSKYZcw2F4nMaO5qwsL5A8RmioAKW81YktRpIQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "5.0.47", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.47.tgz", + "integrity": "sha512-NZQ1hMxY6Hd4ZiinjpU5HUOsyCHiTFkHw7AiM1EVN+Cd3kNTi47flZWNvFwGqnBojntB1+2eBRTeHRuVLtPhEg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "4.0.18", + "@standard-schema/spec": "^1.1.0", + "@workflow/serde": "4.1.0", + "eventsource-parser": "^3.0.8", + "undici": "^7.29.0" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/react": { + "version": "4.0.116", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-4.0.116.tgz", + "integrity": "sha512-DW2NgTsl7KWGZOr/kQpTb54C4gTirWToLqbCvCw44spYX3sr9vKkSQCGKNBGjvJPNKFt3kVvF79oz+ugBfTxbg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/mcp": "2.0.57", + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.47", + "ai": "7.0.113", + "swr": "^2.4.1", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" + } + }, "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": { @@ -73,6 +184,8 @@ }, "node_modules/@ast-grep/napi": { "version": "0.40.5", + "resolved": "https://registry.npmjs.org/@ast-grep/napi/-/napi-0.40.5.tgz", + "integrity": "sha512-hJA62OeBKUQT68DD2gDyhOqJxZxycqg8wLxbqjgqSzYttCMSDL9tiAQ9abgekBYNHudbJosm9sWOEbmCDfpX2A==", "license": "MIT", "engines": { "node": ">= 10" @@ -91,6 +204,8 @@ }, "node_modules/@ast-grep/napi-darwin-arm64": { "version": "0.40.5", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-arm64/-/napi-darwin-arm64-0.40.5.tgz", + "integrity": "sha512-2F072fGN0WTq7KI3okuEnkGJVEHLbi56Bw1H6NAMf7j2mJJeQWsRyGOMcyNnUXZDeNdvoMH0OB2a5wwUegY/nQ==", "cpu": [ "arm64" ], @@ -266,6 +381,8 @@ }, "node_modules/@aws-crypto/sha1-browser": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", @@ -278,6 +395,8 @@ }, "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -289,6 +408,8 @@ }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", @@ -302,6 +423,8 @@ }, "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -313,6 +436,8 @@ }, "node_modules/@aws-crypto/sha256-js": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -325,6 +450,8 @@ }, "node_modules/@aws-crypto/supports-web-crypto": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -332,6 +459,8 @@ }, "node_modules/@aws-crypto/util": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.222.0", @@ -341,6 +470,8 @@ }, "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -352,6 +483,8 @@ }, "node_modules/@aws-sdk/client-cloudfront": { "version": "3.984.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.984.0.tgz", + "integrity": "sha512-couDuDLpJtoeWne/nYyJ+I+5ntBVdNgBVRTCoDaXuVV7OC3u/wz5Ps0+GogspEwMLEFoOJ8t691h3YXQtnpQTw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -402,6 +535,8 @@ }, "node_modules/@aws-sdk/client-dynamodb": { "version": "3.984.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.984.0.tgz", + "integrity": "sha512-8/Oft9MWQtbG6p9f8eY5fsKC2CcO5YVDlwive8eUYS9mEbgnyQxm68OyH26WvsSTykQ9QkIbR+fOG56RsIBODw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -453,6 +588,8 @@ }, "node_modules/@aws-sdk/client-lambda": { "version": "3.984.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.984.0.tgz", + "integrity": "sha512-kqwNBIGNxGVhINwgN/UQfdsQkaMjbu9PFV2EhATWouV+RT60uMjK9JENgLDwbgJmEVbbnPsh9HaZ5KKwPSdiDg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -506,6 +643,8 @@ }, "node_modules/@aws-sdk/client-s3": { "version": "3.984.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.984.0.tgz", + "integrity": "sha512-7ny2Slr93Y+QniuluvcfWwyDi32zWQfznynL56Tk0vVh7bWrvS/odm8WP2nInKicRVNipcJHY2YInur6Q/9V0A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", @@ -570,6 +709,8 @@ }, "node_modules/@aws-sdk/client-sqs": { "version": "3.984.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.984.0.tgz", + "integrity": "sha512-TDvHpOUWlpanc3xQ5Xw0y8L2hoojBFCCSmXQ/6rKqGOf1ScX3dMA+K9aF0Zp0iwjhSh4VvsHD42esl8XwQZDjA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -1018,6 +1159,8 @@ }, "node_modules/@aws-sdk/signature-v4-multi-region": { "version": "3.984.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.984.0.tgz", + "integrity": "sha512-TaWbfYCwnuOSvDSrgs7QgoaoXse49E7LzUkVOUhoezwB7bkmhp+iojADm7UepCEu4021SquD7NG1xA+WCvmldA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.6", @@ -1059,6 +1202,8 @@ }, "node_modules/@aws-sdk/util-endpoints": { "version": "3.984.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.984.0.tgz", + "integrity": "sha512-9ebjLA0hMKHeVvXEtTDCCOBtwjb0bOXiuUV06HNeVdgAjH6gj4x4Zwt4IBti83TiyTGOCl5YfZqGx4ehVsasbQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -1122,6 +1267,8 @@ }, "node_modules/@babel/code-frame": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { @@ -1135,6 +1282,8 @@ }, "node_modules/@babel/compat-data": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -1143,6 +1292,8 @@ }, "node_modules/@babel/core": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { @@ -1172,6 +1323,8 @@ }, "node_modules/@babel/generator": { "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { @@ -1187,6 +1340,8 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { @@ -1202,6 +1357,8 @@ }, "node_modules/@babel/helper-globals": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -1210,6 +1367,8 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { @@ -1222,6 +1381,8 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { @@ -1238,6 +1399,8 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -1246,6 +1409,8 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -1254,6 +1419,8 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -1262,6 +1429,8 @@ }, "node_modules/@babel/helpers": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { @@ -1274,6 +1443,8 @@ }, "node_modules/@babel/parser": { "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { @@ -1305,6 +1476,8 @@ }, "node_modules/@babel/template": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -1318,6 +1491,8 @@ }, "node_modules/@babel/traverse": { "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { @@ -1335,6 +1510,8 @@ }, "node_modules/@babel/types": { "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -1347,6 +1524,8 @@ }, "node_modules/@cloudflare/kv-asset-handler": { "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", + "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", "license": "MIT OR Apache-2.0", "engines": { "node": ">=22.0.0" @@ -1354,6 +1533,8 @@ }, "node_modules/@cloudflare/unenv-preset": { "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz", + "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==", "license": "MIT OR Apache-2.0", "peerDependencies": { "unenv": "2.0.0-rc.24", @@ -1450,6 +1631,8 @@ }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -1460,6 +1643,8 @@ }, "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -1468,6 +1653,8 @@ }, "node_modules/@dotenvx/dotenvx": { "version": "1.31.0", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.31.0.tgz", + "integrity": "sha512-GeDxvtjiRuoyWVU9nQneId879zIyNdL05bS7RKiqMkfBSKpHMWHLoRyRqjYWLaXmX/llKO1hTlqHDmatkQAjPA==", "license": "BSD-3-Clause", "dependencies": { "commander": "^11.1.0", @@ -1490,11 +1677,15 @@ }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", "dev": true, "license": "Apache-2.0" }, "node_modules/@ecies/ciphers": { "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.6.tgz", + "integrity": "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==", "license": "MIT", "engines": { "bun": ">=1", @@ -2490,6 +2681,8 @@ }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2507,6 +2700,8 @@ }, "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2518,6 +2713,8 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -2526,6 +2723,8 @@ }, "node_modules/@eslint/config-array": { "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2539,6 +2738,8 @@ }, "node_modules/@eslint/config-helpers": { "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2550,6 +2751,8 @@ }, "node_modules/@eslint/core": { "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2594,6 +2797,8 @@ }, "node_modules/@eslint/object-schema": { "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2602,6 +2807,8 @@ }, "node_modules/@eslint/plugin-kit": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2644,6 +2851,8 @@ }, "node_modules/@humanfs/core": { "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2655,6 +2864,8 @@ }, "node_modules/@humanfs/node": { "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2668,6 +2879,8 @@ }, "node_modules/@humanfs/types": { "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2676,6 +2889,8 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2688,6 +2903,8 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2700,6 +2917,8 @@ }, "node_modules/@img/colour": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", "engines": { "node": ">=18" @@ -3242,6 +3461,8 @@ }, "node_modules/@isaacs/cliui": { "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -3249,6 +3470,8 @@ }, "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==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3257,6 +3480,8 @@ }, "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": { @@ -3266,6 +3491,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -3273,6 +3500,8 @@ }, "node_modules/@jridgewell/source-map": { "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -3281,16 +3510,45 @@ }, "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==", "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==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.1.0.tgz", + "integrity": "sha512-YdFj5gMRHr0gYNAhTbQDgjLHiEJdIAUYxomhKseUNye7ZGlISQmUoLh2sveFFCiQ7j2YMffT1kgSND0FSfIFjQ==", + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.1.0.tgz", + "integrity": "sha512-FdvwZU5N03O77Suh35JqjAqq5R1ZTEPFtwu6N4BgBq28qrQtrCnpbLL5U2pwYCuE194jAw0xnlOVmIqhfoYuUQ==", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.1.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", @@ -3315,10 +3573,14 @@ }, "node_modules/@next/env": { "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.3.tgz", + "integrity": "sha512-U2eYQRwXj+dsqxV79zFqExDdatnNY/ZWc2nsJU1p/OgT7fd3dXwlF6OjYaFQCfMoeTA19PWq+wVmYgimVA+V+g==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.3.tgz", + "integrity": "sha512-pbEh30vvjKpDoTAmo1v3q2uM4JUi8QaEBpbmjWvGfoec2jLghy/WNtvzAT0bk+Ik9oz6etjt4YjXEk4BQnicCw==", "dev": true, "license": "MIT", "dependencies": { @@ -3328,6 +3590,8 @@ }, "node_modules/@next/swc-darwin-arm64": { "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.3.tgz", + "integrity": "sha512-8Hiv32QJPwdV6KYJ8meR9SBA061tQqnIKTJDocvOXlEQqib0xMFpzArosuffFUUc0sslbh7QQ8a3Yey1QV8EIw==", "cpu": [ "arm64" ], @@ -3466,6 +3730,8 @@ }, "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==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -3476,6 +3742,8 @@ }, "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==", "license": "MIT", "dependencies": { "@noble/hashes": "1.8.0" @@ -3489,6 +3757,8 @@ }, "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==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -3509,6 +3779,8 @@ }, "node_modules/@node-minify/core": { "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@node-minify/core/-/core-8.0.6.tgz", + "integrity": "sha512-/vxN46ieWDLU67CmgbArEvOb41zlYFOkOtr9QW9CnTrBLuTyGgkyNWC2y5+khvRw3Br58p2B5ZVSx/PxCTru6g==", "license": "MIT", "dependencies": { "@node-minify/utils": "8.0.6", @@ -3521,6 +3793,8 @@ }, "node_modules/@node-minify/core/node_modules/brace-expansion": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3528,6 +3802,8 @@ }, "node_modules/@node-minify/core/node_modules/glob": { "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -3544,10 +3820,14 @@ }, "node_modules/@node-minify/core/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, "node_modules/@node-minify/core/node_modules/minimatch": { "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -3561,6 +3841,8 @@ }, "node_modules/@node-minify/core/node_modules/minipass": { "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "license": "ISC", "engines": { "node": ">=8" @@ -3568,6 +3850,8 @@ }, "node_modules/@node-minify/core/node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -3582,6 +3866,8 @@ }, "node_modules/@node-minify/core/node_modules/path-scurry/node_modules/minipass": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -3589,6 +3875,8 @@ }, "node_modules/@node-minify/terser": { "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@node-minify/terser/-/terser-8.0.6.tgz", + "integrity": "sha512-grQ1ipham743ch2c3++C8Isk6toJnxJSyDiwUI/IWUCh4CZFD6aYVw6UAY40IpCnjrq5aXGwiv5OZJn6Pr0hvg==", "license": "MIT", "dependencies": { "@node-minify/utils": "8.0.6", @@ -3600,6 +3888,8 @@ }, "node_modules/@node-minify/utils": { "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@node-minify/utils/-/utils-8.0.6.tgz", + "integrity": "sha512-csY4qcR7jUwiZmkreNTJhcypQfts2aY2CK+a+rXgXUImZiZiySh0FvwHjRnlqWKvg+y6ae9lHFzDRjBTmqlTIQ==", "license": "MIT", "dependencies": { "gzip-size": "6.0.0" @@ -3610,6 +3900,8 @@ }, "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": { @@ -3622,6 +3914,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": { @@ -3630,6 +3924,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": { @@ -3642,6 +3938,8 @@ }, "node_modules/@nolyfill/is-core-module": { "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", "dev": true, "license": "MIT", "engines": { @@ -4142,6 +4440,8 @@ }, "node_modules/@poppinss/colors": { "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", "license": "MIT", "dependencies": { "kleur": "^4.1.5" @@ -4149,6 +4449,8 @@ }, "node_modules/@poppinss/dumper": { "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", "license": "MIT", "dependencies": { "@poppinss/colors": "^4.1.5", @@ -4158,6 +4460,8 @@ }, "node_modules/@poppinss/exception": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", "license": "MIT" }, "node_modules/@radix-ui/number": { @@ -4980,11 +5284,15 @@ }, "node_modules/@rtsao/scc": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", "license": "MIT", "engines": { "node": ">=18" @@ -5119,6 +5427,8 @@ }, "node_modules/@smithy/is-array-buffer": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5307,6 +5617,8 @@ }, "node_modules/@smithy/util-buffer-from": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -5406,10 +5718,20 @@ }, "node_modules/@speed-highlight/core": { "version": "1.2.24", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", + "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", "license": "CC0-1.0" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -5714,6 +6036,8 @@ }, "node_modules/@tsconfig/node18": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-1.0.3.tgz", + "integrity": "sha512-RbwvSJQsuN9TB04AQbGULYfOGE/RnSFk/FLQ5b0NmDf5Kx2q/lABZbHQPKCO1vZ6Fiwkplu+yb9pGdLy1iGseQ==", "license": "MIT" }, "node_modules/@tybys/wasm-util": { @@ -5729,6 +6053,8 @@ }, "node_modules/@types/bcryptjs": { "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", "dev": true, "license": "MIT" }, @@ -5742,21 +6068,68 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", - "dev": true, + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.41", "license": "MIT", @@ -5766,6 +6139,8 @@ }, "node_modules/@types/node-fetch": { "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -5794,7 +6169,6 @@ }, "node_modules/@types/react": { "version": "19.2.15", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -5819,6 +6193,12 @@ "@types/nodemailer": "*" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.59.4", "dev": true, @@ -5991,6 +6371,8 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { @@ -6073,6 +6455,8 @@ }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6082,6 +6466,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", @@ -6112,6 +6502,8 @@ }, "node_modules/@unrs/resolver-binding-darwin-arm64": { "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", "cpu": [ "arm64" ], @@ -6434,8 +6826,25 @@ "win32" ] }, + "node_modules/@vercel/oidc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", + "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@workflow/serde": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@workflow/serde/-/serde-4.1.0.tgz", + "integrity": "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==", + "license": "Apache-2.0" + }, "node_modules/abort-controller": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -6446,6 +6855,8 @@ }, "node_modules/accepts": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -6467,6 +6878,8 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -6475,6 +6888,8 @@ }, "node_modules/agentkeepalive": { "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", "dependencies": { "humanize-ms": "^1.2.1" @@ -6483,8 +6898,27 @@ "node": ">= 8.0.0" } }, + "node_modules/ai": { + "version": "7.0.113", + "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.113.tgz", + "integrity": "sha512-hOowAtiIUXwRqcdVRXll6ToNHYDdfXg+sFF1kYPlNDlgZ63rjR4mp1O29n5lSWZUNcHM1TlSzs30+6/XDUVcAA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "4.0.91", + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.47" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/ajv": { "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -6500,6 +6934,8 @@ }, "node_modules/ansi-colors": { "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "license": "MIT", "engines": { "node": ">=6" @@ -6507,6 +6943,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">=8" @@ -6514,6 +6952,8 @@ }, "node_modules/ansi-styles": { "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" @@ -6524,11 +6964,15 @@ }, "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" @@ -6539,6 +6983,8 @@ }, "node_modules/aria-query": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6547,6 +6993,8 @@ }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "license": "MIT", "dependencies": { @@ -6562,6 +7010,8 @@ }, "node_modules/array-includes": { "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6583,10 +7033,14 @@ }, "node_modules/array-timsort": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", "license": "MIT" }, "node_modules/array.prototype.findlast": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6606,6 +7060,8 @@ }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6626,6 +7082,8 @@ }, "node_modules/array.prototype.flat": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, "license": "MIT", "dependencies": { @@ -6643,6 +7101,8 @@ }, "node_modules/array.prototype.flatmap": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "license": "MIT", "dependencies": { @@ -6660,6 +7120,8 @@ }, "node_modules/array.prototype.tosorted": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, "license": "MIT", "dependencies": { @@ -6675,6 +7137,8 @@ }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6695,11 +7159,15 @@ }, "node_modules/ast-types-flow": { "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true, "license": "MIT" }, "node_modules/async-function": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, "license": "MIT", "engines": { @@ -6708,10 +7176,14 @@ }, "node_modules/asynckit": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/available-typed-arrays": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6726,6 +7198,8 @@ }, "node_modules/aws4fetch": { "version": "1.0.20", + "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.20.tgz", + "integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==", "license": "MIT" }, "node_modules/axe-core": { @@ -6738,14 +7212,28 @@ }, "node_modules/axobject-query": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -6760,6 +7248,8 @@ }, "node_modules/bcryptjs": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", "license": "BSD-3-Clause", "bin": { "bcrypt": "bin/bcrypt" @@ -6769,6 +7259,7 @@ "version": "13.0.3", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.3.tgz", "integrity": "sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==", + "hasInstallScript": true, "license": "MIT", "dependencies": { "node-addon-api": "^8.0.0" @@ -6779,10 +7270,14 @@ }, "node_modules/blake3-wasm": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", "license": "MIT" }, "node_modules/body-parser": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -6805,6 +7300,8 @@ }, "node_modules/body-parser/node_modules/content-type": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { "node": ">=18" @@ -6816,10 +7313,14 @@ }, "node_modules/bowser": { "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -6829,6 +7330,8 @@ }, "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": { @@ -6840,6 +7343,8 @@ }, "node_modules/browserslist": { "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -6872,10 +7377,14 @@ }, "node_modules/buffer-from": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, "node_modules/bytes": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -6883,6 +7392,8 @@ }, "node_modules/call-bind": { "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6900,6 +7411,8 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6911,6 +7424,8 @@ }, "node_modules/call-bound": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6925,6 +7440,8 @@ }, "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": { @@ -6958,8 +7475,20 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -6968,8 +7497,50 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ci-info": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "funding": [ { "type": "github", @@ -6983,6 +7554,8 @@ }, "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" @@ -6993,10 +7566,14 @@ }, "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": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "license": "ISC", "dependencies": { "string-width": "^7.2.0", @@ -7019,6 +7596,8 @@ }, "node_modules/cliui/node_modules/strip-ansi": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -7032,6 +7611,8 @@ }, "node_modules/cloudflare": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/cloudflare/-/cloudflare-4.5.0.tgz", + "integrity": "sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ==", "license": "Apache-2.0", "dependencies": { "@types/node": "^18.11.18", @@ -7045,6 +7626,8 @@ }, "node_modules/cloudflare/node_modules/@types/node": { "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -7052,10 +7635,14 @@ }, "node_modules/cloudflare/node_modules/undici-types": { "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, "node_modules/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" @@ -7063,6 +7650,8 @@ }, "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==", "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -7073,10 +7662,14 @@ }, "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==", "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -7085,8 +7678,20 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "license": "MIT", "engines": { "node": ">=16" @@ -7094,6 +7699,8 @@ }, "node_modules/comment-json": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", + "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", "license": "MIT", "dependencies": { "array-timsort": "^1.0.3", @@ -7105,11 +7712,15 @@ }, "node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/content-disposition": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -7121,6 +7732,8 @@ }, "node_modules/content-type": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7128,11 +7741,15 @@ }, "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": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { "node": ">=18" @@ -7144,6 +7761,8 @@ }, "node_modules/cookie-signature": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -7160,6 +7779,8 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -7172,10 +7793,14 @@ }, "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==", "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==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7189,16 +7814,21 @@ }, "node_modules/csstype": { "version": "3.2.3", - "devOptional": true, + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/data-view-buffer": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7215,6 +7845,8 @@ }, "node_modules/data-view-byte-length": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7231,6 +7863,8 @@ }, "node_modules/data-view-byte-offset": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7251,6 +7885,8 @@ }, "node_modules/debug": { "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7273,13 +7909,30 @@ "node": ">=0.10.0" } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/define-data-property": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "license": "MIT", "dependencies": { @@ -7296,6 +7949,8 @@ }, "node_modules/define-properties": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", "dependencies": { @@ -7312,6 +7967,8 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -7319,13 +7976,26 @@ }, "node_modules/depd": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -7333,8 +8003,23 @@ }, "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/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", @@ -7343,6 +8028,8 @@ }, "node_modules/doctrine": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7354,6 +8041,8 @@ }, "node_modules/dotenv": { "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -7364,6 +8053,8 @@ }, "node_modules/drizzle-kit": { "version": "0.31.10", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", + "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", "dev": true, "license": "MIT", "dependencies": { @@ -7862,6 +8553,8 @@ }, "node_modules/drizzle-orm": { "version": "0.45.2", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", + "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", "license": "Apache-2.0", "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", @@ -7985,6 +8678,8 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -7997,10 +8692,14 @@ }, "node_modules/duplexer": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "license": "MIT" }, "node_modules/eciesjs": { "version": "0.4.18", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.18.tgz", + "integrity": "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==", "license": "MIT", "dependencies": { "@ecies/ciphers": "^0.2.5", @@ -8016,6 +8715,8 @@ }, "node_modules/ee-first": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/electron-to-chromium": { @@ -8025,11 +8726,15 @@ }, "node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -8049,6 +8754,8 @@ }, "node_modules/enquirer": { "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1", @@ -8060,6 +8767,8 @@ }, "node_modules/error-stack-parser-es": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -8067,6 +8776,8 @@ }, "node_modules/es-abstract": { "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -8134,6 +8845,8 @@ }, "node_modules/es-define-property": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -8141,6 +8854,8 @@ }, "node_modules/es-errors": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -8184,6 +8899,8 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8197,6 +8914,8 @@ }, "node_modules/es-shim-unscopables": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, "license": "MIT", "dependencies": { @@ -8266,6 +8985,8 @@ }, "node_modules/escalade": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -8273,10 +8994,14 @@ }, "node_modules/escape-html": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { @@ -8346,6 +9071,8 @@ }, "node_modules/eslint-config-next": { "version": "16.3.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.3.tgz", + "integrity": "sha512-teqtsR26tnlfXFHfVLTM/4tzEzU8DMu6GS1sddZzhfGzgd2f2ofbgDUcsk6cssSCzX6Tk6fmWifJcdANSdPJrw==", "dev": true, "license": "MIT", "dependencies": { @@ -8371,6 +9098,8 @@ }, "node_modules/eslint-config-next/node_modules/globals": { "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", "dev": true, "license": "MIT", "engines": { @@ -8382,6 +9111,8 @@ }, "node_modules/eslint-import-resolver-node": { "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8392,6 +9123,8 @@ }, "node_modules/eslint-import-resolver-node/node_modules/debug": { "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8400,6 +9133,8 @@ }, "node_modules/eslint-import-resolver-typescript": { "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", "dev": true, "license": "ISC", "dependencies": { @@ -8449,6 +9184,8 @@ }, "node_modules/eslint-module-utils/node_modules/debug": { "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8457,6 +9194,8 @@ }, "node_modules/eslint-plugin-import": { "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", "dependencies": { @@ -8489,6 +9228,8 @@ }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8497,6 +9238,8 @@ }, "node_modules/eslint-plugin-jsx-a11y": { "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8525,6 +9268,8 @@ }, "node_modules/eslint-plugin-react": { "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", "dependencies": { @@ -8556,6 +9301,8 @@ }, "node_modules/eslint-plugin-react-hooks": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", "dependencies": { @@ -8574,6 +9321,8 @@ }, "node_modules/eslint-scope": { "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -8589,6 +9338,8 @@ }, "node_modules/eslint-visitor-keys": { "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -8600,6 +9351,8 @@ }, "node_modules/eslint/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": { @@ -8614,6 +9367,8 @@ }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -8629,6 +9384,8 @@ }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -8640,6 +9397,8 @@ }, "node_modules/espree": { "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -8656,6 +9415,8 @@ }, "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==", "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -8667,6 +9428,8 @@ }, "node_modules/esquery": { "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8678,6 +9441,8 @@ }, "node_modules/esrecurse": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -8689,14 +9454,28 @@ }, "node_modules/estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -8705,6 +9484,8 @@ }, "node_modules/etag": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8712,13 +9493,26 @@ }, "node_modules/event-target-shim": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/execa": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -8740,6 +9534,8 @@ }, "node_modules/express": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -8781,18 +9577,30 @@ }, "node_modules/express/node_modules/cookie": { "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "license": "MIT", "dependencies": { @@ -8808,6 +9616,8 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -8819,11 +9629,15 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, @@ -8870,6 +9684,8 @@ }, "node_modules/fdir": { "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", "engines": { "node": ">=12.0.0" @@ -8885,6 +9701,8 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8896,6 +9714,8 @@ }, "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": { @@ -8907,6 +9727,8 @@ }, "node_modules/finalhandler": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -8926,6 +9748,8 @@ }, "node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -8941,6 +9765,8 @@ }, "node_modules/flat-cache": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -8958,6 +9784,8 @@ }, "node_modules/for-each": { "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { @@ -8972,6 +9800,8 @@ }, "node_modules/foreground-child": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -8986,6 +9816,8 @@ }, "node_modules/foreground-child/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==", "license": "ISC", "engines": { "node": ">=14" @@ -8996,6 +9828,8 @@ }, "node_modules/form-data": { "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -9010,10 +9844,14 @@ }, "node_modules/form-data-encoder": { "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", "license": "MIT" }, "node_modules/form-data/node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -9021,6 +9859,8 @@ }, "node_modules/form-data/node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -9031,6 +9871,8 @@ }, "node_modules/formdata-node": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", "license": "MIT", "dependencies": { "node-domexception": "1.0.0", @@ -9042,6 +9884,8 @@ }, "node_modules/forwarded": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -9049,6 +9893,8 @@ }, "node_modules/fresh": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -9056,10 +9902,14 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "license": "MIT", "optional": true, "os": [ @@ -9071,6 +9921,8 @@ }, "node_modules/function-bind": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9097,6 +9949,8 @@ }, "node_modules/functions-have-names": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "license": "MIT", "funding": { @@ -9105,6 +9959,8 @@ }, "node_modules/generator-function": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", "dev": true, "license": "MIT", "engines": { @@ -9113,6 +9969,8 @@ }, "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": { @@ -9121,6 +9979,8 @@ }, "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==", "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -9128,6 +9988,8 @@ }, "node_modules/get-east-asian-width": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "license": "MIT", "engines": { "node": ">=18" @@ -9138,6 +10000,8 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -9160,6 +10024,8 @@ }, "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" @@ -9167,6 +10033,8 @@ }, "node_modules/get-proto": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -9178,6 +10046,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">=10" @@ -9188,6 +10058,8 @@ }, "node_modules/get-symbol-description": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "license": "MIT", "dependencies": { @@ -9215,6 +10087,8 @@ }, "node_modules/glob": { "version": "12.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-12.0.0.tgz", + "integrity": "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==", "license": "BlueOak-1.0.0", "dependencies": { "foreground-child": "^3.3.1", @@ -9236,6 +10110,8 @@ }, "node_modules/glob-parent": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { @@ -9247,6 +10123,8 @@ }, "node_modules/glob/node_modules/balanced-match": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -9277,6 +10155,8 @@ }, "node_modules/globals": { "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", "engines": { @@ -9288,6 +10168,8 @@ }, "node_modules/globalthis": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9303,6 +10185,8 @@ }, "node_modules/gopd": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9313,11 +10197,15 @@ }, "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/gzip-size": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", "license": "MIT", "dependencies": { "duplexer": "^0.1.2" @@ -9331,6 +10219,8 @@ }, "node_modules/has-bigints": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", "engines": { @@ -9342,6 +10232,8 @@ }, "node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -9350,6 +10242,8 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", "dependencies": { @@ -9361,6 +10255,8 @@ }, "node_modules/has-proto": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9375,6 +10271,8 @@ }, "node_modules/has-symbols": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9385,6 +10283,8 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -9398,6 +10298,8 @@ }, "node_modules/hasown": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -9406,21 +10308,77 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", "dev": true, "license": "MIT" }, "node_modules/hermes-parser": { "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", "dev": true, "license": "MIT", "dependencies": { "hermes-estree": "0.25.1" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/http-errors": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -9439,6 +10397,8 @@ }, "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==", "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -9446,6 +10406,8 @@ }, "node_modules/humanize-ms": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "license": "MIT", "dependencies": { "ms": "^2.0.0" @@ -9467,6 +10429,8 @@ }, "node_modules/ignore": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "license": "MIT", "engines": { "node": ">= 4" @@ -9474,6 +10438,8 @@ }, "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": { @@ -9489,6 +10455,8 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -9497,10 +10465,20 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", "dependencies": { @@ -9514,6 +10492,8 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -9525,8 +10505,34 @@ "integrity": "sha512-Bm6H79i01DjgGTCWjUuCjJ6QDo1HB96PT/xCYuyJUP9WFbVDrLSbG4EZCvOCun2rNswZb0c3e4Jt/ws795esHA==", "license": "MIT" }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { @@ -9543,6 +10549,8 @@ }, "node_modules/is-async-function": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9561,6 +10569,8 @@ }, "node_modules/is-bigint": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9575,6 +10585,8 @@ }, "node_modules/is-boolean-object": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { @@ -9590,6 +10602,8 @@ }, "node_modules/is-bun-module": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9609,6 +10623,8 @@ }, "node_modules/is-callable": { "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", "engines": { @@ -9620,6 +10636,8 @@ }, "node_modules/is-core-module": { "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -9634,6 +10652,8 @@ }, "node_modules/is-data-view": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { @@ -9650,6 +10670,8 @@ }, "node_modules/is-date-object": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { @@ -9663,8 +10685,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -9673,6 +10707,8 @@ }, "node_modules/is-finalizationregistry": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", "dependencies": { @@ -9696,6 +10732,8 @@ }, "node_modules/is-generator-function": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", "dependencies": { @@ -9714,6 +10752,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": { @@ -9723,8 +10763,20 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", "engines": { @@ -9736,6 +10788,8 @@ }, "node_modules/is-negative-zero": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "license": "MIT", "engines": { @@ -9747,6 +10801,8 @@ }, "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": { @@ -9755,6 +10811,8 @@ }, "node_modules/is-number-object": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", "dependencies": { @@ -9768,12 +10826,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-promise": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-regex": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { @@ -9791,6 +10865,8 @@ }, "node_modules/is-set": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", "engines": { @@ -9802,6 +10878,8 @@ }, "node_modules/is-shared-array-buffer": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", "dependencies": { @@ -9816,6 +10894,8 @@ }, "node_modules/is-stream": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" @@ -9826,6 +10906,8 @@ }, "node_modules/is-string": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", "dependencies": { @@ -9841,6 +10923,8 @@ }, "node_modules/is-symbol": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { @@ -9857,6 +10941,8 @@ }, "node_modules/is-typed-array": { "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9871,6 +10957,8 @@ }, "node_modules/is-weakmap": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", "engines": { @@ -9882,6 +10970,8 @@ }, "node_modules/is-weakref": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { @@ -9896,6 +10986,8 @@ }, "node_modules/is-weakset": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9911,11 +11003,15 @@ }, "node_modules/isarray": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -9923,6 +11019,8 @@ }, "node_modules/iterator.prototype": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, "license": "MIT", "dependencies": { @@ -9939,6 +11037,8 @@ }, "node_modules/jackspeak": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^9.0.0" @@ -9964,6 +11064,8 @@ }, "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" }, @@ -9990,6 +11092,8 @@ }, "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": { @@ -10001,21 +11105,35 @@ }, "node_modules/json-buffer": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/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": { @@ -10027,6 +11145,8 @@ }, "node_modules/jsx-ast-utils": { "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10041,6 +11161,8 @@ }, "node_modules/keyv": { "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -10049,6 +11171,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">=6" @@ -10056,11 +11180,15 @@ }, "node_modules/language-subtag-registry": { "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/language-tags": { "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dev": true, "license": "MIT", "dependencies": { @@ -10072,6 +11200,8 @@ }, "node_modules/levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10084,6 +11214,8 @@ }, "node_modules/lightningcss": { "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -10133,6 +11265,8 @@ }, "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -10353,6 +11487,8 @@ }, "node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -10367,11 +11503,25 @@ }, "node_modules/lodash.merge": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10383,6 +11533,8 @@ }, "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": { @@ -10398,19 +11550,315 @@ }, "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/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/media-typer": { "version": "1.1.0", "license": "MIT", @@ -10420,6 +11868,8 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -10430,18 +11880,587 @@ }, "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==", "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/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.2.tgz", + "integrity": "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -10454,6 +12473,8 @@ }, "node_modules/micromatch/node_modules/picomatch": { "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -10465,6 +12486,8 @@ }, "node_modules/mime-db": { "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -10472,6 +12495,8 @@ }, "node_modules/mime-types": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -10486,6 +12511,8 @@ }, "node_modules/mimetext": { "version": "3.0.28", + "resolved": "https://registry.npmjs.org/mimetext/-/mimetext-3.0.28.tgz", + "integrity": "sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -10500,6 +12527,8 @@ }, "node_modules/mimetext/node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -10507,6 +12536,8 @@ }, "node_modules/mimetext/node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -10517,6 +12548,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">=6" @@ -10539,6 +12572,8 @@ }, "node_modules/minimatch": { "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -10550,6 +12585,8 @@ }, "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": { @@ -10558,6 +12595,8 @@ }, "node_modules/minipass": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -10565,6 +12604,8 @@ }, "node_modules/mkdirp": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -10575,6 +12616,8 @@ }, "node_modules/mnemonist": { "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", "license": "MIT", "dependencies": { "obliterator": "^1.6.1" @@ -10582,10 +12625,14 @@ }, "node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/nanoid": { "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", "funding": [ { "type": "github", @@ -10602,6 +12649,8 @@ }, "node_modules/napi-postinstall": { "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -10616,6 +12665,8 @@ }, "node_modules/natural-compare": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, @@ -10628,6 +12679,8 @@ }, "node_modules/next": { "version": "16.3.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.3.tgz", + "integrity": "sha512-tuRTx1nQ/yVw83cwJBo9F+njGUgMn3UHQycreWHB8XsStvvAh1AthbI8/4IpKnFaF58F+iSiHejYOlMQ/eq83g==", "license": "MIT", "dependencies": { "@next/env": "16.3.3", @@ -10679,6 +12732,8 @@ }, "node_modules/next/node_modules/@img/sharp-darwin-arm64": { "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", + "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", "cpu": [ "arm64" ], @@ -10740,6 +12795,8 @@ }, "node_modules/next/node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", + "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", "cpu": [ "arm64" ], @@ -11214,6 +13271,8 @@ }, "node_modules/next/node_modules/semver": { "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "optional": true, "bin": { @@ -11225,6 +13284,8 @@ }, "node_modules/next/node_modules/sharp": { "version": "0.35.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", + "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -11282,6 +13343,8 @@ }, "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==", "funding": [ { "type": "github", @@ -11316,6 +13379,8 @@ }, "node_modules/node-fetch": { "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -11351,6 +13416,8 @@ }, "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==", "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -11361,6 +13428,8 @@ }, "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": { @@ -11369,6 +13438,8 @@ }, "node_modules/object-inspect": { "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -11379,6 +13450,8 @@ }, "node_modules/object-keys": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", "engines": { @@ -11387,6 +13460,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">= 10" @@ -11394,6 +13469,8 @@ }, "node_modules/object.assign": { "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "license": "MIT", "dependencies": { @@ -11413,6 +13490,8 @@ }, "node_modules/object.entries": { "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, "license": "MIT", "dependencies": { @@ -11427,6 +13506,8 @@ }, "node_modules/object.fromentries": { "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11444,6 +13525,8 @@ }, "node_modules/object.groupby": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11457,6 +13540,8 @@ }, "node_modules/object.values": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, "license": "MIT", "dependencies": { @@ -11474,10 +13559,14 @@ }, "node_modules/obliterator": { "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", "license": "MIT" }, "node_modules/on-finished": { "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -11488,6 +13577,8 @@ }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -11495,6 +13586,8 @@ }, "node_modules/onetime": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -11508,6 +13601,8 @@ }, "node_modules/optionator": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -11540,6 +13635,8 @@ }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11554,6 +13651,8 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -11577,10 +13676,14 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, "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": { @@ -11590,8 +13693,35 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parseurl": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -11599,6 +13729,8 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "license": "MIT", "engines": { "node": ">=8" @@ -11619,6 +13751,8 @@ }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" @@ -11626,11 +13760,15 @@ }, "node_modules/path-parse": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", @@ -11652,14 +13790,20 @@ }, "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==", "license": "MIT" }, "node_modules/pathe": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "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": { @@ -11672,6 +13816,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -11683,6 +13836,8 @@ }, "node_modules/possible-typed-array-names": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, "license": "MIT", "engines": { @@ -11695,6 +13850,8 @@ }, "node_modules/postcss": { "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "funding": [ { "type": "opencollective", @@ -11737,6 +13894,8 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -11745,6 +13904,8 @@ }, "node_modules/prop-types": { "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "license": "MIT", "dependencies": { @@ -11753,8 +13914,20 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -11766,6 +13939,8 @@ }, "node_modules/punycode": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -11966,6 +14141,8 @@ }, "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": [ { @@ -11992,6 +14169,8 @@ }, "node_modules/raw-body": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -12022,9 +14201,38 @@ }, "node_modules/react-is": { "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.2", "license": "MIT", @@ -12050,6 +14258,8 @@ }, "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", @@ -12070,6 +14280,8 @@ }, "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", @@ -12090,6 +14302,8 @@ }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, "license": "MIT", "dependencies": { @@ -12111,6 +14325,8 @@ }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", "dependencies": { @@ -12128,6 +14344,72 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12145,6 +14427,8 @@ }, "node_modules/resolve": { "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12167,6 +14451,8 @@ }, "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": { @@ -12175,6 +14461,8 @@ }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", "funding": { @@ -12183,6 +14471,8 @@ }, "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": { @@ -12192,6 +14482,8 @@ }, "node_modules/router": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -12206,6 +14498,8 @@ }, "node_modules/router/node_modules/path-to-regexp": { "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -12214,6 +14508,8 @@ }, "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": [ { @@ -12236,6 +14532,8 @@ }, "node_modules/safe-array-concat": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { @@ -12254,6 +14552,8 @@ }, "node_modules/safe-push-apply": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, "license": "MIT", "dependencies": { @@ -12269,6 +14569,8 @@ }, "node_modules/safe-regex-test": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "license": "MIT", "dependencies": { @@ -12285,14 +14587,20 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/scheduler": { "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "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": { @@ -12301,6 +14609,8 @@ }, "node_modules/send": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { "debug": "^4.4.3", @@ -12325,6 +14635,8 @@ }, "node_modules/serve-static": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -12348,6 +14660,8 @@ }, "node_modules/set-function-length": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "license": "MIT", "dependencies": { @@ -12364,6 +14678,8 @@ }, "node_modules/set-function-name": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12378,6 +14694,8 @@ }, "node_modules/set-proto": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "dev": true, "license": "MIT", "dependencies": { @@ -12391,6 +14709,8 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/sharp": { @@ -12447,6 +14767,8 @@ }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -12457,6 +14779,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" @@ -12495,6 +14819,8 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -12511,6 +14837,8 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -12528,6 +14856,8 @@ }, "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==", "license": "ISC" }, "node_modules/smtp-server": { @@ -12546,6 +14876,8 @@ }, "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==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12553,6 +14885,8 @@ }, "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" @@ -12560,19 +14894,35 @@ }, "node_modules/source-map-support": { "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stable-hash": { "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", "dev": true, "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12580,6 +14930,8 @@ }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12592,6 +14944,8 @@ }, "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==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -12617,10 +14971,14 @@ }, "node_modules/string-width/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==", "license": "MIT" }, "node_modules/string-width/node_modules/strip-ansi": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -12634,6 +14992,8 @@ }, "node_modules/string.prototype.includes": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -12647,6 +15007,8 @@ }, "node_modules/string.prototype.matchall": { "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, "license": "MIT", "dependencies": { @@ -12673,6 +15035,8 @@ }, "node_modules/string.prototype.repeat": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, "license": "MIT", "dependencies": { @@ -12719,6 +15083,8 @@ }, "node_modules/string.prototype.trimstart": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "license": "MIT", "dependencies": { @@ -12733,8 +15099,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12745,6 +15127,8 @@ }, "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": { @@ -12753,6 +15137,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">=6" @@ -12760,6 +15146,8 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -12779,8 +15167,28 @@ ], "license": "MIT" }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", "dependencies": { "client-only": "0.0.1" @@ -12802,6 +15210,8 @@ }, "node_modules/supports-color": { "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "license": "MIT", "engines": { "node": ">=18" @@ -12812,6 +15222,8 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { @@ -12821,8 +15233,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.5.1.tgz", + "integrity": "sha512-BRw55e8r0B7SpDN20CAzoQAHl7y1yP7/Zt7oqUjMv0vSt2u2Xnkm88Ws+VypbV9BXHQVuSuyVq7zMjO16wSExw==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwind-merge": { "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", "license": "MIT", "funding": { "type": "github", @@ -12836,6 +15263,8 @@ }, "node_modules/tapable": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", "engines": { @@ -12848,6 +15277,8 @@ }, "node_modules/terser": { "version": "5.16.9", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.9.tgz", + "integrity": "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.2", @@ -12864,8 +15295,22 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "dev": true, @@ -12883,6 +15328,8 @@ }, "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": { @@ -12894,6 +15341,8 @@ }, "node_modules/toidentifier": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -12901,10 +15350,34 @@ }, "node_modules/tr46": { "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -12916,10 +15389,14 @@ }, "node_modules/ts-tqdm": { "version": "0.8.6", + "resolved": "https://registry.npmjs.org/ts-tqdm/-/ts-tqdm-0.8.6.tgz", + "integrity": "sha512-3X3M1PZcHtgQbnwizL+xU8CAgbYbeLHrrDwL9xxcZZrV5J+e7loJm1XrXozHjSkl44J0Zg0SgA8rXbh83kCkcQ==", "license": "MIT" }, "node_modules/tsconfig-paths": { "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "license": "MIT", "dependencies": { @@ -12931,6 +15408,8 @@ }, "node_modules/tsconfig-paths/node_modules/json5": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "license": "MIT", "dependencies": { @@ -12942,6 +15421,8 @@ }, "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/tsx": { @@ -12965,6 +15446,8 @@ }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -12976,6 +15459,8 @@ }, "node_modules/type-is": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { "content-type": "^2.0.0", @@ -13003,6 +15488,8 @@ }, "node_modules/typed-array-buffer": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "license": "MIT", "dependencies": { @@ -13016,6 +15503,8 @@ }, "node_modules/typed-array-byte-length": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, "license": "MIT", "dependencies": { @@ -13034,6 +15523,8 @@ }, "node_modules/typed-array-byte-offset": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13073,6 +15564,8 @@ }, "node_modules/typescript": { "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -13107,6 +15600,8 @@ }, "node_modules/unbox-primitive": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "license": "MIT", "dependencies": { @@ -13124,6 +15619,8 @@ }, "node_modules/undici": { "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -13131,17 +15628,110 @@ }, "node_modules/undici-types": { "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, "node_modules/unenv": { "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "license": "MIT", "dependencies": { "pathe": "^2.0.3" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unpipe": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -13149,6 +15739,8 @@ }, "node_modules/unrs-resolver": { "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -13214,6 +15806,8 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -13222,10 +15816,14 @@ }, "node_modules/urlpattern-polyfill": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", "license": "MIT" }, "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" @@ -13245,6 +15843,8 @@ }, "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", @@ -13263,15 +15863,56 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vary": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", "license": "MIT", "engines": { "node": ">= 14" @@ -13279,10 +15920,14 @@ }, "node_modules/webidl-conversions": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -13291,6 +15936,8 @@ }, "node_modules/which": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "license": "ISC", "dependencies": { "isexe": "^3.1.1" @@ -13304,6 +15951,8 @@ }, "node_modules/which-boxed-primitive": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, "license": "MIT", "dependencies": { @@ -13322,6 +15971,8 @@ }, "node_modules/which-builtin-type": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -13348,6 +15999,8 @@ }, "node_modules/which-collection": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "license": "MIT", "dependencies": { @@ -13391,6 +16044,8 @@ }, "node_modules/word-wrap": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -13415,6 +16070,30 @@ "@cloudflare/workerd-windows-64": "1.20260828.1" } }, + "node_modules/workers-ai-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/workers-ai-provider/-/workers-ai-provider-4.0.0.tgz", + "integrity": "sha512-FkhwMP1EP/MKXR8Jv1o7wLVKfTqu0h/PUaEmhZ7gyDiioib5Wsh3oPAKhG+7+SWfU3roTDHaEWlJpkJjpXu+fQ==", + "license": "MIT", + "peerDependencies": { + "@ai-sdk/anthropic": "^4.0.0", + "@ai-sdk/google": "^4.0.0", + "@ai-sdk/openai": "^4.0.0", + "@ai-sdk/provider": "^4.0.0", + "ai": "^7.0.0" + }, + "peerDependenciesMeta": { + "@ai-sdk/anthropic": { + "optional": true + }, + "@ai-sdk/google": { + "optional": true + }, + "@ai-sdk/openai": { + "optional": true + } + } + }, "node_modules/wrangler": { "version": "4.127.1", "license": "MIT OR Apache-2.0", @@ -13514,6 +16193,8 @@ }, "node_modules/wrangler/node_modules/@esbuild/darwin-arm64": { "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -13864,6 +16545,8 @@ }, "node_modules/wrangler/node_modules/esbuild": { "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -13903,6 +16586,8 @@ }, "node_modules/wrap-ansi": { "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", @@ -13928,6 +16613,8 @@ }, "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -13941,10 +16628,14 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/ws": { "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -13977,6 +16668,8 @@ }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { "node": ">=10" @@ -13984,11 +16677,15 @@ }, "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/yaml": { "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -14017,6 +16714,8 @@ }, "node_modules/yargs-parser": { "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" @@ -14024,6 +16723,8 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -14035,6 +16736,8 @@ }, "node_modules/youch": { "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", "license": "MIT", "dependencies": { "@poppinss/colors": "^4.1.5", @@ -14046,6 +16749,8 @@ }, "node_modules/youch-core": { "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", "license": "MIT", "dependencies": { "@poppinss/exception": "^1.2.2", @@ -14054,6 +16759,8 @@ }, "node_modules/zod": { "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -14061,6 +16768,8 @@ }, "node_modules/zod-validation-error": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", "dev": true, "license": "MIT", "engines": { @@ -14069,6 +16778,16 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 32b5a0b..51b5f4a 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,9 @@ }, "license": "SEE LICENSE IN LICENSE", "dependencies": { + "@ai-sdk/openai-compatible": "^3.0.55", + "@ai-sdk/react": "^4.0.116", + "@modelcontextprotocol/server": "^2.1.0", "@opennextjs/cloudflare": "^1.19.9", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -67,6 +70,7 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toast": "^1.2.15", "@tanstack/react-query": "^5.100.11", + "ai": "^7.0.113", "bcryptjs": "^3.0.3", "better-sqlite3": "^13.0.3", "class-variance-authority": "^0.7.1", @@ -82,8 +86,12 @@ "qrcode": "^1.5.4", "react": "^19.1.7", "react-dom": "^19.1.7", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "sass": "^1.105.0", "smtp-server": "^3.19.12", "tailwind-merge": "^3.6.0", + "workers-ai-provider": "^4.0.0", "zod": "^4.4.3" }, "devDependencies": { diff --git a/server/index.ts b/server/index.ts index f27a4dc..4ea35af 100644 --- a/server/index.ts +++ b/server/index.ts @@ -6,6 +6,7 @@ import { WebSocketServer } from "ws"; import { getUserFromSession } from "@/lib/auth/session"; import { getSessionTokenFromRequest } from "@/lib/realtime/utils"; import { processInboundMessage } from "@/lib/email/inbound"; +import { processAgentDraftJob } from "@/lib/agent/jobs/utils"; import { processOutboundQueue, type OutboundQueueMessage } from "@/lib/email/send"; import { processWebhookRetry, type WebhookRetryMessage } from "@/lib/email/webhooks"; import { isInboundQueueMessage, isWebhookRetryMessage } from "../worker-utils"; @@ -37,6 +38,9 @@ async function main() { if (isWebhookRetryMessage(body)) await processWebhookRetry(env, body as WebhookRetryMessage); else await processOutboundQueue(env, body as OutboundQueueMessage); }); + runtime.agentQueue.setConsumer(async (body) => { + if (typeof body === "object" && body !== null && (body as { kind?: unknown }).kind === "agent.draft" && typeof (body as { jobId?: unknown }).jobId === "string") await processAgentDraftJob(env, (body as { jobId: string }).jobId); + }); const app = next({ dev, dir: process.cwd(), hostname: host, port }); const handle = app.getRequestHandler(); @@ -87,6 +91,7 @@ async function main() { stopScheduler(); runtime.inboundQueue.stop(); runtime.outboundQueue.stop(); + runtime.agentQueue.stop(); server.close(() => process.exit(0)); setTimeout(() => process.exit(0), 5000).unref(); }; diff --git a/server/runtime/env.ts b/server/runtime/env.ts index c3d3de3..0bc0a9a 100644 --- a/server/runtime/env.ts +++ b/server/runtime/env.ts @@ -36,6 +36,7 @@ export function createNodeRuntime(): NodeRuntime { const mailer = openMailer(mailerConfig()); const inboundQueue = openQueue("mailflare-inbound"); const outboundQueue = openQueue("mailflare-outbound"); + const agentQueue = openQueue("mailflare-agent"); const realtime = new RealtimeHubRegistry(); const publicDir = resolve(optional("PUBLIC_DIR") ?? "./public"); @@ -45,11 +46,16 @@ export function createNodeRuntime(): NodeRuntime { EMAIL: mailer, INBOUND_QUEUE: inboundQueue, OUTBOUND_QUEUE: outboundQueue, + AGENT_QUEUE: agentQueue, + AI_MODEL: optional("AI_MODEL"), + AI_BASE_URL: optional("AI_BASE_URL"), + AI_API_KEY: optional("AI_API_KEY"), REALTIME: realtime.namespace(), ASSETS: openAssets(publicDir), IMAGES: undefined as unknown as CloudflareEnv["IMAGES"], WORKER_SELF_REFERENCE: undefined as unknown as CloudflareEnv["WORKER_SELF_REFERENCE"], LOGIN_RATE_LIMIT: openRateLimiter(20, 60), + AGENT_RATE_LIMIT: openRateLimiter(120, 60), CF_TOKEN: optional("CF_TOKEN"), CF_API_KEY: optional("CF_API_KEY"), CF_EMAIL: optional("CF_EMAIL"), @@ -70,6 +76,7 @@ export function createNodeRuntime(): NodeRuntime { mailer: mailer as unknown as Mailer, inboundQueue: inboundQueue as unknown as InProcessQueue, outboundQueue: outboundQueue as unknown as InProcessQueue, + agentQueue: agentQueue as unknown as InProcessQueue, realtime, }; } diff --git a/server/runtime/scheduler.ts b/server/runtime/scheduler.ts index f2c2b16..c989fa1 100644 --- a/server/runtime/scheduler.ts +++ b/server/runtime/scheduler.ts @@ -1,9 +1,11 @@ import { runScheduledDatabaseBackup } from "@/lib/backups/runner"; +import { runAgentMaintenance } from "@/lib/agent/maintenance"; /** Fire the daily 02:00 UTC backup, matching the cron trigger in wrangler.jsonc. */ export function startScheduler(env: CloudflareEnv) { let lastRunDay = ""; const timer = setInterval(() => { + runAgentMaintenance(env).catch((error) => console.error("Agent maintenance failed", error)); const now = new Date(); const day = now.toISOString().slice(0, 10); if (now.getUTCHours() !== 2 || lastRunDay === day) return; diff --git a/server/runtime/types.d.ts b/server/runtime/types.d.ts index bf76b6b..b094086 100644 --- a/server/runtime/types.d.ts +++ b/server/runtime/types.d.ts @@ -10,5 +10,6 @@ export type NodeRuntime = { mailer: Mailer; inboundQueue: InProcessQueue; outboundQueue: InProcessQueue; + agentQueue: InProcessQueue; realtime: RealtimeHubRegistry; }; diff --git a/src/app/(admin)/admin/page.tsx b/src/app/(admin)/admin/page.tsx index 434975b..acb3c73 100644 --- a/src/app/(admin)/admin/page.tsx +++ b/src/app/(admin)/admin/page.tsx @@ -1,9 +1,15 @@ import Link from "next/link"; -import { BadgeDollarSign, Globe2, KeyRound, Mail, Palette, Settings, Users, Webhook } from "lucide-react"; +import { BadgeDollarSign, Bot, Globe2, KeyRound, Mail, Palette, Settings, Users, Webhook } from "lucide-react"; import { AdminUpdateCard } from "@/components/admin-update-card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; const sections = [ + { + href: "/agent", + title: "Agent", + description: "Choose the email assistant's AI provider and model.", + icon: Bot, + }, { href: "/mailboxes", title: "Mailboxes", @@ -34,12 +40,12 @@ const sections = [ description: "Add and manage user accounts with a Team license.", icon: Users, }, - // { - // href: "/api-keys", - // title: "API Keys", - // description: "Manage API credentials for programmatic access.", - // icon: KeyRound, - // }, + { + href: "/api-keys", + title: "Admin API keys", + description: "Manage API access to domains, accounts, and mailboxes.", + icon: KeyRound, + }, // { // href: "/webhooks", // title: "Webhooks", diff --git a/src/app/(admin)/agent/page.tsx b/src/app/(admin)/agent/page.tsx new file mode 100644 index 0000000..228c9d3 --- /dev/null +++ b/src/app/(admin)/agent/page.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Bot } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { DEFAULT_CLOUDFLARE_MODEL } from "@/lib/agent/provider-constants"; +import type { AgentModelOption, AgentProviderPreset } from "@/lib/agent/provider-types"; +import type { AgentAdminForm } from "./types"; +import { baseUrlForPreset, loadAgentAdminSettings, loadAgentModels, PROVIDER_PRESETS, saveAgentAdminSettings } from "./utils"; + +const initialForm: AgentAdminForm = { provider: "cloudflare", preset: "openai", baseUrl: "", apiKey: "", model: DEFAULT_CLOUDFLARE_MODEL }; + +export default function AdminAgentPage() { + const [form, setForm] = useState(initialForm); + const [loaded, setLoaded] = useState(false); + const [cloudflareAvailable, setCloudflareAvailable] = useState(false); + const [hasSavedKey, setHasSavedKey] = useState(false); + const [savedEndpoint, setSavedEndpoint] = useState(""); + const [models, setModels] = useState([]); + const [modelSource, setModelSource] = useState<"catalog" | "suggested" | null>(null); + const [modelsLoading, setModelsLoading] = useState(false); + const [modelError, setModelError] = useState(null); + const [status, setStatus] = useState(null); + const [saving, setSaving] = useState(false); + + useEffect(() => { + let active = true; + void loadAgentAdminSettings().then((data) => { + if (!active) return; + setForm({ provider: data.config.provider, preset: data.config.preset, baseUrl: data.config.baseUrl, apiKey: "", model: data.config.model }); + setCloudflareAvailable(data.cloudflareAvailable); + setHasSavedKey(data.config.hasApiKey); + setSavedEndpoint(`${data.config.preset}|${data.config.baseUrl}`); + setLoaded(true); + }).catch((error) => { if (active) setStatus(error instanceof Error ? error.message : "Could not load agent settings"); }); + return () => { active = false; }; + }, []); + + const canUseSavedKey = hasSavedKey && savedEndpoint === `${form.preset}|${form.baseUrl}`; + useEffect(() => { + if (!loaded) return; + setModels([]); + setModelSource(null); + setModelError(null); + if (form.provider === "compatible" && (!form.baseUrl || (!form.apiKey.trim() && !canUseSavedKey))) { + setModelError(form.preset === "custom" && !form.baseUrl ? "Enter an HTTPS base URL to load models." : "Enter an API key to load models."); + return; + } + const controller = new AbortController(); + const timer = window.setTimeout(() => { + setModelsLoading(true); + void loadAgentModels(form, controller.signal).then((data) => { + setModels(data.models); + setModelSource(data.source); + }).catch((error) => { + if (!controller.signal.aborted) setModelError(error instanceof Error ? error.message : "Could not load models"); + }).finally(() => { if (!controller.signal.aborted) setModelsLoading(false); }); + }, form.provider === "cloudflare" ? 0 : 500); + return () => { window.clearTimeout(timer); controller.abort(); }; + }, [loaded, form.provider, form.preset, form.baseUrl, form.apiKey, canUseSavedKey]); + + async function submit(event: React.FormEvent) { + event.preventDefault(); + setSaving(true); + setStatus(null); + try { + const data = await saveAgentAdminSettings(form); + setForm((current) => ({ ...current, apiKey: "" })); + setHasSavedKey(data.config.hasApiKey); + setSavedEndpoint(`${data.config.preset}|${data.config.baseUrl}`); + setStatus("Agent provider saved. New chats and auto-drafts will use this model."); + } catch (error) { + setStatus(error instanceof Error ? error.message : "Could not save agent settings"); + } finally { setSaving(false); } + } + + return
+

Agent

Choose the AI provider and model used by the email assistant for chat and drafts.

+ + AI providerEmail content is sent to the selected provider when someone uses the assistant. Sending a draft still requires human approval. +
+
{form.provider === "cloudflare" && !cloudflareAvailable &&

Cloudflare Workers AI requires an AI binding on this installation.

}
+ {form.provider === "compatible" && <> +
+
setForm((current) => ({ ...current, baseUrl: event.target.value, model: "" }))} required />
+
setForm((current) => ({ ...current, apiKey: event.target.value }))} />

{canUseSavedKey ? "An API key is saved on the server. Enter a new one to replace it." : "The API key is stored on the server and is never shown again."}

+ } +
{modelSource === "suggested" &&

Showing suggested Cloudflare models because a full catalog is unavailable.

}{modelError &&

{modelError}

} setForm((current) => ({ ...current, model: event.target.value }))} placeholder={form.provider === "cloudflare" ? DEFAULT_CLOUDFLARE_MODEL : "provider/model-id"} required />

Choose a model that supports tool calling so the assistant can read mail and create drafts.

+ {status &&

{status}

} + +
+
+
; +} diff --git a/src/app/(admin)/agent/types.d.ts b/src/app/(admin)/agent/types.d.ts new file mode 100644 index 0000000..cec7bc7 --- /dev/null +++ b/src/app/(admin)/agent/types.d.ts @@ -0,0 +1,6 @@ +import type { AgentModelOption, AgentProviderKind, AgentProviderPreset, AgentProviderPublicConfig } from "@/lib/agent/provider-types"; + +export type AgentAdminConfig = AgentProviderPublicConfig; +export type AgentAdminSettingsResponse = { config: AgentAdminConfig; cloudflareAvailable: boolean; error?: string }; +export type AgentAdminModelsResponse = { models: AgentModelOption[]; source: "catalog" | "suggested"; error?: string }; +export type AgentAdminForm = { provider: AgentProviderKind; preset: AgentProviderPreset; baseUrl: string; apiKey: string; model: string }; diff --git a/src/app/(admin)/agent/utils.ts b/src/app/(admin)/agent/utils.ts new file mode 100644 index 0000000..11b217e --- /dev/null +++ b/src/app/(admin)/agent/utils.ts @@ -0,0 +1,36 @@ +import { authFetch } from "@/lib/auth/client"; +import { PROVIDER_BASE_URLS } from "@/lib/agent/provider-constants"; +import type { AgentProviderPreset } from "@/lib/agent/provider-types"; +import type { AgentAdminForm, AgentAdminModelsResponse, AgentAdminSettingsResponse } from "./types"; + +export const PROVIDER_PRESETS: { id: AgentProviderPreset; label: string }[] = [ + { id: "openai", label: "OpenAI" }, + { id: "openrouter", label: "OpenRouter" }, + { id: "groq", label: "Groq" }, + { id: "custom", label: "Custom endpoint" }, +]; + +export function baseUrlForPreset(preset: AgentProviderPreset, current = "") { + return preset === "custom" ? current : PROVIDER_BASE_URLS[preset]; +} + +export async function loadAgentAdminSettings(): Promise { + const response = await authFetch("/api/admin/agent"); + const data = await response.json() as AgentAdminSettingsResponse; + if (!response.ok) throw new Error(data.error || "Could not load agent settings"); + return data; +} + +export async function loadAgentModels(form: AgentAdminForm, signal?: AbortSignal): Promise { + const response = await authFetch("/api/admin/agent/models", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(form), signal }); + const data = await response.json() as AgentAdminModelsResponse; + if (!response.ok) throw new Error(data.error || "Could not load models"); + return data; +} + +export async function saveAgentAdminSettings(form: AgentAdminForm): Promise { + const response = await authFetch("/api/admin/agent", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(form) }); + const data = await response.json() as AgentAdminSettingsResponse; + if (!response.ok) throw new Error(data.error || "Could not save agent settings"); + return data; +} diff --git a/src/app/(admin)/api-keys/page.tsx b/src/app/(admin)/api-keys/page.tsx index af5ad52..a4d904d 100644 --- a/src/app/(admin)/api-keys/page.tsx +++ b/src/app/(admin)/api-keys/page.tsx @@ -2,10 +2,9 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; -import { KeyRound, Plus } from "lucide-react"; +import { Copy, KeyRound, Plus, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { Card, CardContent } from "@/components/ui/card"; import { Dialog, DialogContent, @@ -17,131 +16,135 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; -import { CardGridSkeleton } from "@/components/page-skeletons"; +import { List, ListRow } from "@/components/ui/list"; +import { SectionRowSkeleton } from "@/components/page-skeletons"; import { authFetch } from "@/lib/auth/client"; -import { API_KEY_SCOPES, type ApiKeyScope } from "@/lib/api/scopes"; +import type { AdminApiKeyScope } from "@/lib/api/scopes-types"; import type { ApiKey } from "./types"; -import { parseApiKeyScopes } from "./utils"; +import { ADMIN_KEY_PERMISSIONS, parseApiKeyScopes } from "./utils"; export default function ApiKeysPage() { const qc = useQueryClient(); const [name, setName] = useState(""); - const [scopes, setScopes] = useState(["send", "read"]); + const [scopes, setScopes] = useState(["domains"]); + const [mcpAllowed, setMcpAllowed] = useState(false); const [newKey, setNewKey] = useState(null); + const [copied, setCopied] = useState(false); const [createOpen, setCreateOpen] = useState(false); const { data, isLoading } = useQuery({ - queryKey: ["api-keys"], + queryKey: ["admin-api-keys"], queryFn: async () => { - const res = await authFetch("/api/api-keys"); + const res = await authFetch("/api/admin/api-keys"); + if (!res.ok) throw new Error("Could not load admin API keys"); return (await res.json()) as { apiKeys: ApiKey[] }; }, }); const create = useMutation({ mutationFn: async () => { - const res = await authFetch("/api/api-keys", { + const res = await authFetch("/api/admin/api-keys", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name, scopes }), + body: JSON.stringify({ name: name.trim(), scopes, mcpAllowed }), }); - const json = (await res.json()) as { key?: string }; - if (!res.ok) throw new Error("Failed"); + const json = (await res.json()) as { key?: string; error?: string }; + if (!res.ok || !json.key) throw new Error(typeof json.error === "string" ? json.error : "Could not create API key"); setNewKey(json.key ?? null); + setCopied(false); setName(""); }, onSuccess: () => { - setCreateOpen(false); - setScopes(["send", "read"]); - qc.invalidateQueries({ queryKey: ["api-keys"] }); + setScopes(["domains"]); + qc.invalidateQueries({ queryKey: ["admin-api-keys"] }); }, }); + const revoke = useMutation({ + mutationFn: async (id: string) => { + const res = await authFetch(`/api/admin/api-keys?id=${encodeURIComponent(id)}`, { method: "DELETE" }); + if (!res.ok) throw new Error("Could not revoke admin API key"); + }, + onSuccess: () => qc.invalidateQueries({ queryKey: ["admin-api-keys"] }), + }); return (
-

API Keys

- +

Admin API keys

+ { if (create.isPending) return; setCreateOpen(open); if (!open) { setNewKey(null); setCopied(false); } }}> - + - Create API key - Choose what this key is allowed to do. - -
+ {newKey ? "API key created" : "Create API key"} + {newKey ? "Copy this key now. It will only be shown once." : "Name your key and choose the access it needs."} + + {!newKey &&
- - setName(e.target.value)} placeholder="Production app" /> + + setName(e.target.value)} maxLength={100} placeholder="Production app" />
-
- -
- {API_KEY_SCOPES.map((scope) => ( -
+ {create.isError && (

{(create.error as Error).message}

)} -
+
} + {newKey &&
{newKey}
}
- {newKey && ( - - -

Copy your key now:

- {newKey} -
-
- )}
-
- {(data?.apiKeys ?? []).length} total -
+ {revoke.isError &&

{revoke.error.message}

} {isLoading && ( - + )} {!isLoading && (data?.apiKeys ?? []).length === 0 && ( -

+

No API keys yet

)} -
+ {(data?.apiKeys ?? []).map((key) => ( -
- + - {key.name} + {key.name}{key.kind === "mcp" && MCP} {key.prefix}... {parseApiKeyScopes(key.scopes).map((scope) => ( @@ -151,9 +154,10 @@ export default function ApiKeysPage() { ))} -
+ + ))} -
+
); diff --git a/src/app/(admin)/api-keys/types.d.ts b/src/app/(admin)/api-keys/types.d.ts index 8b2d235..58cd715 100644 --- a/src/app/(admin)/api-keys/types.d.ts +++ b/src/app/(admin)/api-keys/types.d.ts @@ -2,6 +2,7 @@ export type ApiKey = { id: string; name: string; prefix: string; + kind: "legacy" | "mcp"; scopes: string; createdAt?: string; lastUsedAt?: string | null; diff --git a/src/app/(admin)/api-keys/utils.ts b/src/app/(admin)/api-keys/utils.ts index 9ec9fd2..b46238e 100644 --- a/src/app/(admin)/api-keys/utils.ts +++ b/src/app/(admin)/api-keys/utils.ts @@ -1,3 +1,11 @@ +import type { AdminApiKeyScope } from "@/lib/api/scopes-types"; + +export const ADMIN_KEY_PERMISSIONS: { value: AdminApiKeyScope; label: string; description: string }[] = [ + { value: "domains", label: "Manage domains", description: "Add and remove domains, and manage their DNS setup." }, + { value: "accounts", label: "Manage accounts", description: "Create and update accounts. Requires a Team license." }, + { value: "mailboxes", label: "Manage mailboxes", description: "Create, update, and remove mailboxes." }, +]; + export function parseApiKeyScopes(scopes: string): string[] { try { const parsed = JSON.parse(scopes); diff --git a/src/app/(admin)/branding/page.tsx b/src/app/(admin)/branding/page.tsx index 9563438..b213c9f 100644 --- a/src/app/(admin)/branding/page.tsx +++ b/src/app/(admin)/branding/page.tsx @@ -35,7 +35,7 @@ export default function BrandingPage() { This installation continues to use the original Mailflare name, app icon, and favicon. - + diff --git a/src/app/(admin)/licenses/utils.ts b/src/app/(admin)/licenses/utils.ts index ce242fb..785ed75 100644 --- a/src/app/(admin)/licenses/utils.ts +++ b/src/app/(admin)/licenses/utils.ts @@ -7,7 +7,7 @@ import type { ActivatableLicensePlan, LicenseAction, LicenseResponse } from "./t export const LICENSE_PLANS: LicensePlan[] = [ { name: "Pro", - price: 19, + price: 29, originalPrice: 39, description: "A one-time license for one account, including one year of product updates.", features: ["Custom branding", "All future Pro features", "Keep the licensed version forever"], diff --git a/src/app/(dashboard)/agent/review/[id]/page.tsx b/src/app/(dashboard)/agent/review/[id]/page.tsx new file mode 100644 index 0000000..ead99ce --- /dev/null +++ b/src/app/(dashboard)/agent/review/[id]/page.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams, useRouter } from "next/navigation"; +import { authFetch } from "@/lib/auth/client"; +import { SendReview } from "@/components/agent/send-review"; +import type { AgentReviewResponse } from "@/components/agent/send-review-types"; + +export default function AgentReviewPage() { + const { id } = useParams<{ id: string }>(); + const router = useRouter(); + const [request, setRequest] = useState(null); + const [error, setError] = useState(null); + useEffect(() => { + void authFetch(`/api/agent/approvals/${id}`).then(async (response) => { + const data = await response.json() as AgentReviewResponse; + if (!response.ok) throw new Error(data.error || "Could not load review"); + setRequest(data); + }).catch((cause) => setError(cause instanceof Error ? cause.message : "Could not load review")); + }, [id]); + return
+

AI draft review

+ {error &&

{error}

} + {!error && !request &&

Loading review…

} + {request && (request.status !== "pending" || request.stale || !request.snapshot) &&

This review is {request.stale ? "out of date" : request.status}. Open the draft and request a new review if needed.

} + {request?.status === "pending" && !request.stale && request.snapshot && router.push("/drafts")} onSent={() => router.push("/sent")} />} +
; +} diff --git a/src/app/(dashboard)/dashboard-state.ts b/src/app/(dashboard)/dashboard-state.ts new file mode 100644 index 0000000..702492b --- /dev/null +++ b/src/app/(dashboard)/dashboard-state.ts @@ -0,0 +1,51 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { authFetch } from "@/lib/auth/client"; + +const emailPath = /^\/(?:inbox|sent|archived|spam|trash|starred|snoozed|drafts)\/[^/]+$|^\/folders\/[^/]+\/[^/]+$/; + +export function useDashboardState() { + const pathname = usePathname(); + const router = useRouter(); + const [assistantOpen, setAssistantOpen] = useState(false); + const [assistantFullSize, setAssistantFullSize] = useState(false); + const [storagePrefix, setStoragePrefix] = useState(null); + + useEffect(() => { + let cancelled = false; + void authFetch("/api/auth/me", { redirectOnUnauthorized: false }).then(async (response) => { + if (!response.ok) return; + const data = await response.json() as { user?: { id?: string } }; + if (cancelled || !data.user?.id) return; + const prefix = `mailflare-dashboard:${data.user.id}`; + try { + setAssistantOpen(localStorage.getItem(`${prefix}:assistant-open`) === "true"); + setAssistantFullSize(localStorage.getItem(`${prefix}:assistant-full-size`) === "true"); + if (window.location.pathname === "/inbox") { + const savedEmail = localStorage.getItem(`${prefix}:current-email`); + if (savedEmail && emailPath.test(savedEmail)) router.replace(savedEmail); + } + } catch { /* Storage is optional. */ } + setStoragePrefix(prefix); + }).catch(() => undefined); + return () => { cancelled = true; }; + }, []); + + useEffect(() => { + if (!storagePrefix) return; + try { + localStorage.setItem(`${storagePrefix}:assistant-open`, String(assistantOpen)); + localStorage.setItem(`${storagePrefix}:assistant-full-size`, String(assistantFullSize)); + } catch { /* Storage is optional. */ } + }, [storagePrefix, assistantOpen, assistantFullSize]); + + useEffect(() => { + if (!storagePrefix || !emailPath.test(pathname)) return; + try { localStorage.setItem(`${storagePrefix}:current-email`, pathname); } + catch { /* Storage is optional. */ } + }, [storagePrefix, pathname]); + + return { assistantOpen, setAssistantOpen, assistantFullSize, setAssistantFullSize }; +} diff --git a/src/app/(dashboard)/inbox/[messageId]/page.tsx b/src/app/(dashboard)/inbox/[messageId]/page.tsx index 2394868..1989cba 100644 --- a/src/app/(dashboard)/inbox/[messageId]/page.tsx +++ b/src/app/(dashboard)/inbox/[messageId]/page.tsx @@ -1,9 +1,9 @@ "use client"; import Link from "next/link"; -import { useParams } from "next/navigation"; +import { useParams, usePathname } from "next/navigation"; import { useEffect, useState } from "react"; -import { ArrowLeft, Cloud, ExternalLink } from "lucide-react"; +import { ArrowLeft, Cloud, Columns2, ExternalLink, List, Rows2 } from "lucide-react"; import dayjs from "dayjs"; import { MarkAsRead } from "@/components/mark-read"; import { useSelectedMailbox } from "@/components/mailbox-provider"; @@ -21,6 +21,8 @@ import { ThreadMessageActions } from "@/components/messages/thread-message-actio import { SpamScoreDetails } from "@/components/messages/spam-score-details"; import { useMessageThread } from "@/components/messages/use-message-thread"; import { useLatestMessagesFirst } from "@/components/messages/use-latest-messages-first"; +import { useMessageListVisibility } from "@/components/messages/message-list-visibility"; +import { Tooltip } from "@/components/ui/tooltip"; import { getMessageBackHref } from "@/components/message-actions/utils"; import { getEmailAddress, getEmailDisplayName, splitEmailAddressList } from "@/lib/email/address"; import type { MessageAttachment, MessageDetailResponse } from "./types"; @@ -36,9 +38,12 @@ import { import { extractCloudAttachments } from "./cloud-attachment-utils"; import { sanitizeEmailHtml } from "./email-html-sanitizer"; import { collapseQuotedEmailHtml } from "./quote-collapse-utils"; +import clsx from "clsx"; +import { useAssistantOpen } from "@/components/agent/assistant-open-state"; export default function MessageDetailPage() { const params = useParams<{ messageId: string }>(); + const pathname = usePathname(); const { selectedMailbox, mailboxes } = useSelectedMailbox(); const messageId = params.messageId; const [data, setData] = useState(null); @@ -47,8 +52,11 @@ export default function MessageDetailPage() { useState(null); const [threadExpanded, setThreadExpanded] = useState(false); const [latestMessagesFirst] = useLatestMessagesFirst(); + const { visible: messageListVisible, toggle: toggleMessageList } = useMessageListVisibility(); usePageLoading(loading); const thread = useMessageThread(messageId, data?.message?.threadId); + const assistantVisible = useAssistantOpen(); + const isAnyPanelVisible = messageListVisible || assistantVisible; useEffect(() => { let cancelled = false; @@ -139,8 +147,10 @@ export default function MessageDetailPage() { return (
{!message.read && } -
-
+
+ + +
{/*
-
-

- {message.subject ?? "(no subject)"} -

+ +

+ {message.subject ?? "(no subject)"} +

+
+
+ +
- -
-
-
- -
-

- - {message.direction === "inbound" ? ( - +

+
+
+
+ +
+

+ + {message.direction === "inbound" ? ( + + ) : ( + fromName + )} + {" "} + <{fromAddress}> +

+

+ to{" "} + {message.direction === "inbound" && toEntries.length <= 1 ? ( + toName ) : ( - fromName + )} - {" "} - <{fromAddress}> -

-

- to{" "} - {message.direction === "inbound" && toEntries.length <= 1 ? ( - toName - ) : ( - +

+ {ccEntries.length > 0 && ( +

+ cc +

)} + {bccEntries.length > 0 && ( +

+ bcc +

+ )} +
+
+
+

+ {dayjs(message.createdAt).format("MMM DD, YYYY, hh:mmA")}

- {ccEntries.length > 0 && ( -

- cc -

- )} - {bccEntries.length > 0 && ( -

- bcc -

- )} + threadMessage.id === message.id) || !thread.messages.some((threadMessage) => threadMessage.id !== message.id)} + />
-
-

- {dayjs(message.createdAt).format("MMM DD, YYYY, hh:mmA")} -

- +
+ {htmlBody ? ( +
+ ) : ( +
+                {cloudAttachmentResult.content}
+              
+ )} + {quotedHtml && } + {bodyDisplay.quotedContent.map((quotedContent) => ( + + ))}
-
-
- {htmlBody ? ( -
- ) : ( -
-              {cloudAttachmentResult.content}
-            
+ {cloudAttachmentResult.attachments.length > 0 && ( +
+

+ Cloud files ({cloudAttachmentResult.attachments.length}) +

+
+ {cloudAttachmentResult.attachments.map((attachment) => ( + + + + + {attachment.filename} + + + Open from {attachment.provider} + + + + + ))} +
+
+ )} + {attachments.length > 0 && ( +
+

+ Attachments ({attachments.length}) +

+
+ {attachments.map((attachment) => ( + + ))} +
+
)} - {quotedHtml && } - {bodyDisplay.quotedContent.map((quotedContent) => ( - - ))}
- {cloudAttachmentResult.attachments.length > 0 && ( -
-

- Cloud files ({cloudAttachmentResult.attachments.length}) -

-
- {cloudAttachmentResult.attachments.map((attachment) => ( - - - - - {attachment.filename} - - - Open from {attachment.provider} - - - - - ))} -
-
- )} - {attachments.length > 0 && ( -
-

- Attachments ({attachments.length}) -

-
- {attachments.map((attachment) => ( - - ))} -
-
- )}
@@ -45,11 +52,19 @@ export default function DashboardLayout({ + -
- {children} -
+
+ +
+ {children} +
+
+ +
diff --git a/src/app/(settings)/settings/api-keys/page.tsx b/src/app/(settings)/settings/api-keys/page.tsx new file mode 100644 index 0000000..a9fe137 --- /dev/null +++ b/src/app/(settings)/settings/api-keys/page.tsx @@ -0,0 +1,5 @@ +import { ApiKeysSettings } from "@/components/settings/api-keys-settings"; + +export default function ApiKeysSettingsPage() { + return
; +} diff --git a/src/app/(settings)/settings/app-passwords/page.tsx b/src/app/(settings)/settings/app-passwords/page.tsx new file mode 100644 index 0000000..d7d5210 --- /dev/null +++ b/src/app/(settings)/settings/app-passwords/page.tsx @@ -0,0 +1,15 @@ +import { EmailClientsSettings } from "@/components/settings/email-clients-settings"; + +export default function EmailAppsSettingsPage() { + return
+
+
+

App Passwords

+

Use your mail from a desktop or mobile app over JMAP.

+
+
+ +
+
+
; +} diff --git a/src/app/(settings)/settings/security/page.tsx b/src/app/(settings)/settings/security/page.tsx new file mode 100644 index 0000000..7c0d8e7 --- /dev/null +++ b/src/app/(settings)/settings/security/page.tsx @@ -0,0 +1,27 @@ +import { ChangePasswordForm } from "@/components/settings/change-password-form"; +import { MfaSettings } from "@/components/settings/mfa-settings"; + +export default function SecuritySettingsPage() { + return
+
+
+

Security

+

Manage how you sign in to your account.

+
+
+
+

Change password

+

Use at least 8 characters for your new password.

+
+ +
+
+
+

Two-factor authentication

+

Require a code from an authenticator app when signing in.

+
+ +
+
+
; +} diff --git a/src/app/api/admin/agent/models/route.ts b/src/app/api/admin/agent/models/route.ts new file mode 100644 index 0000000..1852325 --- /dev/null +++ b/src/app/api/admin/agent/models/route.ts @@ -0,0 +1 @@ +export { POST } from "./utils"; diff --git a/src/app/api/admin/agent/models/utils.ts b/src/app/api/admin/agent/models/utils.ts new file mode 100644 index 0000000..1a2cfc2 --- /dev/null +++ b/src/app/api/admin/agent/models/utils.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import { getEnv } from "@/lib/cloudflare"; +import { requireSessionUser } from "@/lib/api/auth"; +import { listCloudflareAgentModels, listCompatibleAgentModels } from "@/lib/agent/provider-models"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; + +const requestSchema = z.object({ + provider: z.enum(["cloudflare", "compatible"]), + preset: z.enum(["openai", "openrouter", "groq", "custom"]), + baseUrl: z.string().max(500), + apiKey: z.string().max(2_000).optional(), +}); + +export async function POST(request: Request) { + const env = getEnv(); + const session = await requireSessionUser(env, request); + if (session.error) return session.error; + if (session.user.role !== "admin") return Response.json({ error: "Forbidden" }, { status: 403 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const parsed = requestSchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return Response.json({ error: "Invalid provider details" }, { status: 400 }); + try { + const result = parsed.data.provider === "cloudflare" ? await listCloudflareAgentModels(env) : await listCompatibleAgentModels(env, parsed.data); + return Response.json(result, { headers: { "Cache-Control": "no-store" } }); + } catch (error) { + return Response.json({ error: error instanceof Error ? error.message : "Unable to load models" }, { status: 400 }); + } +} diff --git a/src/app/api/admin/agent/route.ts b/src/app/api/admin/agent/route.ts new file mode 100644 index 0000000..b5fe44b --- /dev/null +++ b/src/app/api/admin/agent/route.ts @@ -0,0 +1 @@ +export { GET, PUT } from "./utils"; diff --git a/src/app/api/admin/agent/utils.ts b/src/app/api/admin/agent/utils.ts new file mode 100644 index 0000000..53daa90 --- /dev/null +++ b/src/app/api/admin/agent/utils.ts @@ -0,0 +1,59 @@ +import { z } from "zod"; +import { getDb } from "@/db"; +import { appSettings } from "@/db/schema"; +import { getEnv } from "@/lib/cloudflare"; +import { requireSessionUser } from "@/lib/api/auth"; +import { getAgentProviderConfig, getAgentProviderPublicConfig, resolveAgentBaseUrl } from "@/lib/agent/provider"; +import { AGENT_SETTINGS_ID, DEFAULT_CLOUDFLARE_MODEL } from "@/lib/agent/provider-constants"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; + +const providerSchema = z.object({ + provider: z.enum(["cloudflare", "compatible"]), + preset: z.enum(["openai", "openrouter", "groq", "custom"]), + baseUrl: z.string().max(500), + apiKey: z.string().max(2_000).optional(), + model: z.string().trim().min(1).max(200), +}); + +async function authorize(request: Request) { + const env = getEnv(); + const session = await requireSessionUser(env, request); + if (session.error) return { env, error: session.error }; + if (session.user.role !== "admin") return { env, error: Response.json({ error: "Forbidden" }, { status: 403 }) }; + return { env, error: null }; +} + +export async function GET(request: Request) { + const access = await authorize(request); + if (access.error) return access.error; + return Response.json({ config: await getAgentProviderPublicConfig(access.env), cloudflareAvailable: !!access.env.AI }, { headers: { "Cache-Control": "no-store" } }); +} + +export async function PUT(request: Request) { + const access = await authorize(request); + if (access.error) return access.error; + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const parsed = providerSchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return Response.json({ error: "Invalid provider settings" }, { status: 400 }); + const input = parsed.data; + if (input.provider === "cloudflare" && !access.env.AI) return Response.json({ error: "Cloudflare Workers AI binding is unavailable" }, { status: 400 }); + let baseUrl: string | null = null; + let apiKey: string | null = null; + if (input.provider === "compatible") { + try { baseUrl = resolveAgentBaseUrl(input.preset, input.baseUrl); } + catch { return Response.json({ error: "Enter a valid HTTPS provider base URL" }, { status: 400 }); } + const current = await getAgentProviderConfig(access.env); + apiKey = input.apiKey?.trim() || (current.provider === "compatible" && current.preset === input.preset && current.baseUrl === baseUrl ? current.apiKey : "") || null; + if (!apiKey) return Response.json({ error: "Enter an API key for this provider" }, { status: 400 }); + } + const values = { + agentProvider: input.provider, + agentPreset: input.provider === "compatible" ? input.preset : null, + agentBaseUrl: baseUrl, + agentApiKey: apiKey, + agentModel: input.provider === "cloudflare" ? input.model || DEFAULT_CLOUDFLARE_MODEL : input.model, + updatedAt: new Date(), + }; + await getDb(access.env).insert(appSettings).values({ id: AGENT_SETTINGS_ID, ...values }).onConflictDoUpdate({ target: appSettings.id, set: values }); + return Response.json({ config: await getAgentProviderPublicConfig(access.env), cloudflareAvailable: !!access.env.AI }, { headers: { "Cache-Control": "no-store" } }); +} diff --git a/src/app/api/admin/api-keys/route.ts b/src/app/api/admin/api-keys/route.ts new file mode 100644 index 0000000..3200ede --- /dev/null +++ b/src/app/api/admin/api-keys/route.ts @@ -0,0 +1 @@ +export { GET, POST, DELETE } from "./utils"; diff --git a/src/app/api/admin/api-keys/utils.ts b/src/app/api/admin/api-keys/utils.ts new file mode 100644 index 0000000..05d3c26 --- /dev/null +++ b/src/app/api/admin/api-keys/utils.ts @@ -0,0 +1,75 @@ +import { NextResponse } from "next/server"; +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { getEnv } from "@/lib/cloudflare"; +import { getDb } from "@/db"; +import { agentSendApprovals, apiKeys } from "@/db/schema"; +import { requireSessionUser } from "@/lib/api/auth"; +import { generateApiKey, parseScopes, scopesToJson } from "@/lib/api-keys"; +import { ADMIN_API_KEY_SCOPES } from "@/lib/api/scopes"; +import { newId } from "@/lib/ids"; + +const adminScopes = new Set(ADMIN_API_KEY_SCOPES); +const createKeySchema = z.object({ + name: z.string().trim().min(1).max(100), + scopes: z.array(z.enum([...ADMIN_API_KEY_SCOPES])).min(1), + mcpAllowed: z.boolean().default(false), +}); + +async function authorize(request: Request) { + const env = getEnv(); + const session = await requireSessionUser(env, request); + if (session.error) return { env, user: null, error: session.error }; + if (session.user.role !== "admin") return { env, user: null, error: NextResponse.json({ error: "Forbidden" }, { status: 403 }) }; + return { env, user: session.user, error: null }; +} + +export async function GET(request: Request) { + const access = await authorize(request); + if (access.error) return access.error; + const rows = await getDb(access.env).select({ + id: apiKeys.id, + name: apiKeys.name, + prefix: apiKeys.prefix, + kind: apiKeys.kind, + scopes: apiKeys.scopes, + createdAt: apiKeys.createdAt, + lastUsedAt: apiKeys.lastUsedAt, + }).from(apiKeys).where(eq(apiKeys.userId, access.user!.id)); + return NextResponse.json({ apiKeys: rows.map((row) => ({ ...row, scopes: scopesToJson(parseScopes(row.scopes).filter((scope) => adminScopes.has(scope))) })).filter((row) => parseScopes(row.scopes).length > 0) }); +} + +export async function POST(request: Request) { + const access = await authorize(request); + if (access.error) return access.error; + if (request.headers.get("Origin") !== new URL(request.url).origin) return NextResponse.json({ error: "Invalid origin" }, { status: 403 }); + const parsed = createKeySchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); + const db = getDb(access.env); + const { fullKey, prefix, hash } = generateApiKey(); + const id = newId("key"); + await db.insert(apiKeys).values({ + id, + kind: parsed.data.mcpAllowed ? "mcp" : "legacy", + userId: access.user!.id, + name: parsed.data.name, + prefix, + keyHash: hash, + scopes: scopesToJson([...new Set(parsed.data.scopes)]), + }); + return NextResponse.json({ key: fullKey }); +} + +export async function DELETE(request: Request) { + const access = await authorize(request); + if (access.error) return access.error; + if (request.headers.get("Origin") !== new URL(request.url).origin) return NextResponse.json({ error: "Invalid origin" }, { status: 403 }); + const id = new URL(request.url).searchParams.get("id"); + if (!id) return NextResponse.json({ error: "Key required" }, { status: 400 }); + const db = getDb(access.env); + const [key] = await db.select({ id: apiKeys.id, scopes: apiKeys.scopes }).from(apiKeys).where(and(eq(apiKeys.id, id), eq(apiKeys.userId, access.user!.id))).limit(1); + if (!key || !parseScopes(key.scopes).some((scope) => adminScopes.has(scope))) return NextResponse.json({ error: "Key not found" }, { status: 404 }); + await db.update(agentSendApprovals).set({ status: "cancelled" }).where(and(eq(agentSendApprovals.requestKeyId, id), eq(agentSendApprovals.status, "pending"))); + await db.delete(apiKeys).where(eq(apiKeys.id, id)); + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/agent/actions/confirm/route.ts b/src/app/api/agent/actions/confirm/route.ts new file mode 100644 index 0000000..f25b227 --- /dev/null +++ b/src/app/api/agent/actions/confirm/route.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; +import { confirmAgentAction } from "@/lib/agent/actions"; + +const schema = z.object({ toolMessageId: z.string().min(1) }); + +export async function POST(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const parsed = schema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return Response.json({ error: "Invalid request" }, { status: 400 }); + try { return Response.json(await confirmAgentAction(env, user, parsed.data.toolMessageId)); } + catch (error) { return Response.json({ error: error instanceof Error ? error.message : "Could not approve action" }, { status: 409 }); } +} diff --git a/src/app/api/agent/approvals/[id]/confirm/route.ts b/src/app/api/agent/approvals/[id]/confirm/route.ts new file mode 100644 index 0000000..0aa8f7c --- /dev/null +++ b/src/app/api/agent/approvals/[id]/confirm/route.ts @@ -0,0 +1,15 @@ +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { confirmAgentSend } from "@/lib/agent/approvals/utils"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; + +type Params = { params: Promise<{ id: string }> }; + +export async function POST(request: Request, { params }: Params) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + try { return Response.json(await confirmAgentSend(env, user, (await params).id)); } + catch (error) { return Response.json({ error: error instanceof Error ? error.message : "Send could not be confirmed" }, { status: 409 }); } +} diff --git a/src/app/api/agent/approvals/[id]/route.ts b/src/app/api/agent/approvals/[id]/route.ts new file mode 100644 index 0000000..a46f4eb --- /dev/null +++ b/src/app/api/agent/approvals/[id]/route.ts @@ -0,0 +1,13 @@ +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { getAgentSendRequest } from "@/lib/agent/approvals/utils"; + +type Params = { params: Promise<{ id: string }> }; + +export async function GET(request: Request, { params }: Params) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + try { return Response.json(await getAgentSendRequest(env, user, (await params).id)); } + catch { return Response.json({ error: "Approval not found" }, { status: 404 }); } +} diff --git a/src/app/api/agent/approvals/route.ts b/src/app/api/agent/approvals/route.ts new file mode 100644 index 0000000..e6881dc --- /dev/null +++ b/src/app/api/agent/approvals/route.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { requestAgentSend } from "@/lib/agent/approvals/utils"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; + +const schema = z.object({ draftId: z.string().min(1), expectedRevision: z.number().int().positive() }); + +export async function POST(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const parsed = schema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return Response.json({ error: "Invalid request" }, { status: 400 }); + try { return Response.json(await requestAgentSend(env, user, parsed.data.draftId, parsed.data.expectedRevision)); } + catch (error) { return Response.json({ error: error instanceof Error ? error.message : "Could not create approval" }, { status: 409 }); } +} diff --git a/src/app/api/agent/chat/route.ts b/src/app/api/agent/chat/route.ts new file mode 100644 index 0000000..c59075b --- /dev/null +++ b/src/app/api/agent/chat/route.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { createAgentChatStream } from "@/lib/agent/chat"; +import { allowAgentRequest } from "@/lib/agent/rate-limit"; +import { readJsonBody } from "@/lib/http/request"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; +import { agentProviderErrorMessage } from "@/lib/agent/errors"; + +const schema = z.object({ mailboxId: z.string().min(1), conversationId: z.string().nullish(), text: z.string().trim().min(1).max(4_000) }); + +export async function POST(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + if (!await allowAgentRequest(env, `chat:${user.id}`)) return Response.json({ error: "Rate limit exceeded" }, { status: 429 }); + const raw = await readJsonBody(request, 8_000).catch(() => null); + const parsed = schema.safeParse(raw); + if (!parsed.success) return Response.json({ error: "Invalid request" }, { status: 400 }); + try { + const { conversationId, stream } = await createAgentChatStream({ env, user, mailboxId: parsed.data.mailboxId, origin: "chat" }, parsed.data.text, parsed.data.conversationId ?? undefined, request.signal); + return new Response(stream, { headers: { "Content-Type": "application/x-ndjson; charset=utf-8", "Cache-Control": "no-store", "X-Conversation-Id": conversationId } }); + } catch (error) { + return Response.json({ error: agentProviderErrorMessage(error) }, { status: 400 }); + } +} diff --git a/src/app/api/agent/conversations/[id]/route.ts b/src/app/api/agent/conversations/[id]/route.ts new file mode 100644 index 0000000..42f96c5 --- /dev/null +++ b/src/app/api/agent/conversations/[id]/route.ts @@ -0,0 +1,30 @@ +import { and, eq } from "drizzle-orm"; +import { getDb } from "@/db"; +import { agentConversations } from "@/db/schema"; +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { getAgentChatHistory } from "@/lib/agent/chat"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; + +type Params = { params: Promise<{ id: string }> }; + +export async function GET(request: Request, { params }: Params) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + const { id } = await params; + const mailboxId = new URL(request.url).searchParams.get("mailboxId"); + if (!mailboxId) return Response.json({ error: "Mailbox required" }, { status: 400 }); + try { return Response.json({ messages: await getAgentChatHistory({ env, user, mailboxId, origin: "chat" }, id) }); } + catch { return Response.json({ error: "Conversation not found" }, { status: 404 }); } +} + +export async function DELETE(request: Request, { params }: Params) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const { id } = await params; + await getDb(env).delete(agentConversations).where(and(eq(agentConversations.id, id), eq(agentConversations.userId, user.id))); + return Response.json({ ok: true }); +} diff --git a/src/app/api/agent/conversations/route.ts b/src/app/api/agent/conversations/route.ts new file mode 100644 index 0000000..6629715 --- /dev/null +++ b/src/app/api/agent/conversations/route.ts @@ -0,0 +1,16 @@ +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { listAgentConversations } from "@/lib/agent/chat"; + +export async function GET(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + const mailboxId = new URL(request.url).searchParams.get("mailboxId"); + if (!mailboxId) return Response.json({ error: "Mailbox required" }, { status: 400 }); + try { + return Response.json({ conversations: await listAgentConversations({ env, user, mailboxId, origin: "chat" }) }); + } catch { + return Response.json({ error: "Mailbox not found" }, { status: 404 }); + } +} diff --git a/src/app/api/agent/jobs/[id]/retry/route.ts b/src/app/api/agent/jobs/[id]/retry/route.ts new file mode 100644 index 0000000..8bf41fb --- /dev/null +++ b/src/app/api/agent/jobs/[id]/retry/route.ts @@ -0,0 +1,24 @@ +import { and, eq } from "drizzle-orm"; +import { getDb } from "@/db"; +import { agentJobs } from "@/db/schema"; +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { getMailboxAccessLevel } from "@/lib/mailboxes/access"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; + +type Params = { params: Promise<{ id: string }> }; + +export async function POST(request: Request, { params }: Params) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const { id } = await params; + const db = getDb(env); + const [job] = await db.select().from(agentJobs).where(eq(agentJobs.id, id)).limit(1); + if (!job || job.reviewerUserId !== user.id || !(await getMailboxAccessLevel(db, user, job.mailboxId))?.canSendOnBehalf) return Response.json({ error: "Job not found" }, { status: 404 }); + if (job.status !== "failed") return Response.json({ error: "Job cannot be retried" }, { status: 409 }); + await db.update(agentJobs).set({ status: "pending", attempts: 0, nextAttemptAt: new Date(), leaseUntil: null, reason: null }).where(and(eq(agentJobs.id, id), eq(agentJobs.status, "failed"))); + await env.AGENT_QUEUE?.send({ kind: "agent.draft", jobId: id }); + return Response.json({ ok: true }); +} diff --git a/src/app/api/agent/jobs/route.ts b/src/app/api/agent/jobs/route.ts new file mode 100644 index 0000000..aa15930 --- /dev/null +++ b/src/app/api/agent/jobs/route.ts @@ -0,0 +1,16 @@ +import { and, desc, eq } from "drizzle-orm"; +import { getDb } from "@/db"; +import { agentJobs } from "@/db/schema"; +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { getMailboxAccessLevel } from "@/lib/mailboxes/access"; + +export async function GET(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + const mailboxId = new URL(request.url).searchParams.get("mailboxId"); + if (!mailboxId || !(await getMailboxAccessLevel(getDb(env), user, mailboxId))?.canRead) return Response.json({ error: "Mailbox not found" }, { status: 404 }); + const rows = await getDb(env).select({ id: agentJobs.id, status: agentJobs.status, reason: agentJobs.reason, draftId: agentJobs.draftId, sourceMessageId: agentJobs.sourceMessageId, createdAt: agentJobs.createdAt }).from(agentJobs).where(and(eq(agentJobs.mailboxId, mailboxId), eq(agentJobs.reviewerUserId, user.id))).orderBy(desc(agentJobs.createdAt)).limit(30); + return Response.json({ jobs: rows }); +} diff --git a/src/app/api/agent/mcp-keys/route.ts b/src/app/api/agent/mcp-keys/route.ts new file mode 100644 index 0000000..372c02e --- /dev/null +++ b/src/app/api/agent/mcp-keys/route.ts @@ -0,0 +1,56 @@ +import { and, eq, inArray } from "drizzle-orm"; +import { z } from "zod"; +import { getDb } from "@/db"; +import { agentSendApprovals, apiKeys, mcpKeyMailboxes } from "@/db/schema"; +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { generateApiKey, parseScopes, scopesToJson } from "@/lib/api-keys"; +import { ADMIN_API_KEY_SCOPES } from "@/lib/api/scopes"; +import { getMailboxAccessLevel } from "@/lib/mailboxes/access"; +import { newId } from "@/lib/ids"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; + +const createSchema = z.object({ name: z.string().trim().min(1).max(100), mailboxIds: z.array(z.string()).min(1).max(30), scopes: z.array(z.enum(["mcp:read", "mcp:draft", "mcp:organize", "mcp:request-send"])).min(1) }); +const ADMIN_SCOPES = new Set(ADMIN_API_KEY_SCOPES); + +export async function GET(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + const db = getDb(env); + const rows = await db.select({ id: apiKeys.id, name: apiKeys.name, prefix: apiKeys.prefix, scopes: apiKeys.scopes, lastUsedAt: apiKeys.lastUsedAt, createdAt: apiKeys.createdAt }).from(apiKeys).where(and(eq(apiKeys.userId, user.id), eq(apiKeys.kind, "mcp"))); + const keys = rows.filter((key) => !parseScopes(key.scopes).some((scope) => ADMIN_SCOPES.has(scope))); + const allowed = keys.length ? await db.select().from(mcpKeyMailboxes).where(inArray(mcpKeyMailboxes.keyId, keys.map((key) => key.id))) : []; + return Response.json({ keys: keys.map((key) => ({ ...key, mailboxIds: allowed.filter((row) => row.keyId === key.id).map((row) => row.mailboxId) })) }); +} + +export async function POST(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const parsed = createSchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return Response.json({ error: "Invalid key request" }, { status: 400 }); + const db = getDb(env); + const mailboxIds = [...new Set(parsed.data.mailboxIds)]; + for (const mailboxId of mailboxIds) if (!(await getMailboxAccessLevel(db, user, mailboxId))?.canRead) return Response.json({ error: "Mailbox not found" }, { status: 403 }); + const { fullKey, prefix, hash } = generateApiKey(); + const id = newId("key"); + await db.insert(apiKeys).values({ id, kind: "mcp", userId: user.id, name: parsed.data.name, prefix, keyHash: hash, scopes: scopesToJson([...new Set(parsed.data.scopes)]) }); + await db.insert(mcpKeyMailboxes).values(mailboxIds.map((mailboxId) => ({ keyId: id, mailboxId }))); + return Response.json({ id, name: parsed.data.name, prefix, key: fullKey, mailboxIds, scopes: parsed.data.scopes }); +} + +export async function DELETE(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const id = new URL(request.url).searchParams.get("id"); + if (!id) return Response.json({ error: "Key required" }, { status: 400 }); + const [owned] = await getDb(env).select({ id: apiKeys.id }).from(apiKeys).where(and(eq(apiKeys.id, id), eq(apiKeys.userId, user.id), eq(apiKeys.kind, "mcp"))).limit(1); + if (!owned) return Response.json({ error: "Key not found" }, { status: 404 }); + await getDb(env).update(agentSendApprovals).set({ status: "cancelled" }).where(and(eq(agentSendApprovals.requestKeyId, id), eq(agentSendApprovals.status, "pending"))); + await getDb(env).delete(apiKeys).where(and(eq(apiKeys.id, id), eq(apiKeys.userId, user.id), eq(apiKeys.kind, "mcp"))); + return Response.json({ ok: true }); +} diff --git a/src/app/api/agent/settings/route.ts b/src/app/api/agent/settings/route.ts new file mode 100644 index 0000000..d9bf73e --- /dev/null +++ b/src/app/api/agent/settings/route.ts @@ -0,0 +1,51 @@ +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { getDb } from "@/db"; +import { agentJobs, agentSendApprovals, mailboxAgentSettings, users } from "@/db/schema"; +import { getEnv } from "@/lib/cloudflare"; +import { getCurrentUser } from "@/lib/auth/cookies"; +import { getMailboxAccessLevel } from "@/lib/mailboxes/access"; +import { getAgentProviderPublicConfig } from "@/lib/agent/provider"; +import { hasValidSessionMutationOrigin } from "@/lib/auth/origin"; + +const schema = z.object({ mailboxId: z.string().min(1), enabled: z.boolean(), autoDraftEnabled: z.boolean(), reviewerUserId: z.string().nullable().optional(), instructions: z.string().max(4_000), dailyLimit: z.number().int().min(1).max(100) }); + +export async function GET(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + const mailboxId = new URL(request.url).searchParams.get("mailboxId"); + if (!mailboxId) return Response.json({ error: "Mailbox required" }, { status: 400 }); + const db = getDb(env); + const access = await getMailboxAccessLevel(db, user, mailboxId); + if (!access?.canRead) return Response.json({ error: "Mailbox not found" }, { status: 404 }); + const [settings] = await db.select().from(mailboxAgentSettings).where(eq(mailboxAgentSettings.mailboxId, mailboxId)).limit(1); + const reviewers: { id: string; name: string; email: string }[] = []; + if (access.canManage) { + const candidates = await db.select({ id: users.id, name: users.name, email: users.email, disabled: users.disabled }).from(users).limit(200); + for (const candidate of candidates) if (!candidate.disabled && (await getMailboxAccessLevel(db, { id: candidate.id, role: "user" }, mailboxId))?.canSendOnBehalf) reviewers.push({ id: candidate.id, name: candidate.name, email: candidate.email }); + } + const agentProvider = await getAgentProviderPublicConfig(env); + const provider = agentProvider.configured ? { kind: agentProvider.provider === "cloudflare" ? "Cloudflare Workers AI" : agentProvider.preset === "custom" ? "OpenAI-compatible API" : agentProvider.preset, model: agentProvider.model } : null; + return Response.json({ settings: settings ?? { mailboxId, enabled: true, autoDraftEnabled: false, reviewerUserId: access.mailbox.userId, instructions: "", dailyLimit: 25 }, canManage: access.canManage, canConfigureProvider: user.role === "admin", providerConfigured: agentProvider.configured, provider, autoReplyEnabled: access.mailbox.autoReplyEnabled, reviewers }); +} + +export async function PUT(request: Request) { + const env = getEnv(); + const user = await getCurrentUser(env, request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + if (!hasValidSessionMutationOrigin(request)) return Response.json({ error: "Invalid origin" }, { status: 403 }); + const parsed = schema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return Response.json({ error: "Invalid settings" }, { status: 400 }); + const db = getDb(env); + const access = await getMailboxAccessLevel(db, user, parsed.data.mailboxId); + if (!access?.canManage) return Response.json({ error: "Mailbox not found or permission denied" }, { status: 403 }); + const reviewerId = parsed.data.reviewerUserId || access.mailbox.userId; + const [reviewer] = await db.select().from(users).where(eq(users.id, reviewerId)).limit(1); + if (!reviewer || reviewer.disabled || !(await getMailboxAccessLevel(db, reviewer, parsed.data.mailboxId))?.canSendOnBehalf) return Response.json({ error: "Reviewer needs mailbox send permission" }, { status: 400 }); + if (parsed.data.autoDraftEnabled && access.mailbox.autoReplyEnabled) return Response.json({ error: "Disable the out-of-office auto-reply before enabling AI drafts" }, { status: 400 }); + await db.insert(mailboxAgentSettings).values({ ...parsed.data, reviewerUserId: reviewerId }).onConflictDoUpdate({ target: mailboxAgentSettings.mailboxId, set: { enabled: parsed.data.enabled, autoDraftEnabled: parsed.data.autoDraftEnabled, reviewerUserId: reviewerId, instructions: parsed.data.instructions, dailyLimit: parsed.data.dailyLimit, updatedAt: new Date() } }); + if (!parsed.data.enabled || !parsed.data.autoDraftEnabled) await db.update(agentJobs).set({ status: "skipped", reason: "Auto-drafting disabled" }).where(and(eq(agentJobs.mailboxId, parsed.data.mailboxId), eq(agentJobs.status, "pending"))); + if (!parsed.data.enabled) await db.update(agentSendApprovals).set({ status: "cancelled" }).where(and(eq(agentSendApprovals.mailboxId, parsed.data.mailboxId), eq(agentSendApprovals.status, "pending"))); + return Response.json({ ok: true }); +} diff --git a/src/app/api/api-keys/route.ts b/src/app/api/api-keys/route.ts index b689a88..3e0ca40 100644 --- a/src/app/api/api-keys/route.ts +++ b/src/app/api/api-keys/route.ts @@ -1,16 +1,16 @@ import { NextResponse } from "next/server"; -import { eq } from "drizzle-orm"; +import { and, eq, inArray } from "drizzle-orm"; import { z } from "zod"; import { getEnv } from "@/lib/cloudflare"; import { getDb } from "@/db"; -import { apiKeys } from "@/db/schema"; +import { agentSendApprovals, apiKeys, mcpKeyMailboxes } from "@/db/schema"; import { requireUser } from "@/lib/auth/cookies"; -import { generateApiKey, scopesToJson } from "@/lib/api-keys"; +import { generateApiKey, parseScopes, scopesToJson } from "@/lib/api-keys"; import { API_KEY_SCOPES } from "@/lib/api/scopes"; import { newId } from "@/lib/ids"; const createKeySchema = z.object({ - name: z.string().min(1), + name: z.string().trim().min(1).max(100), scopes: z.array(z.enum([...API_KEY_SCOPES])).min(1), }); @@ -23,19 +23,23 @@ export async function GET(request: Request) { id: apiKeys.id, name: apiKeys.name, prefix: apiKeys.prefix, + kind: apiKeys.kind, scopes: apiKeys.scopes, createdAt: apiKeys.createdAt, lastUsedAt: apiKeys.lastUsedAt, }) .from(apiKeys) .where(eq(apiKeys.userId, user.id)); - return NextResponse.json({ apiKeys: rows }); + const mcpIds = rows.filter((row) => row.kind === "mcp").map((row) => row.id); + const allowed = mcpIds.length ? await db.select().from(mcpKeyMailboxes).where(inArray(mcpKeyMailboxes.keyId, mcpIds)) : []; + return NextResponse.json({ apiKeys: rows.filter((row) => !parseScopes(row.scopes).some((scope) => ["domains", "accounts", "mailboxes"].includes(scope))).map((row) => ({ ...row, mailboxIds: allowed.filter((item) => item.keyId === row.id).map((item) => item.mailboxId) })) }); } export async function POST(request: Request) { const env = getEnv(); const user = await requireUser(env, request); - const parsed = createKeySchema.safeParse(await request.json()); + if (request.headers.get("Origin") !== new URL(request.url).origin) return NextResponse.json({ error: "Invalid origin" }, { status: 403 }); + const parsed = createKeySchema.safeParse(await request.json().catch(() => null)); if (!parsed.success) { return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); } @@ -49,8 +53,23 @@ export async function POST(request: Request) { name: parsed.data.name, prefix, keyHash: hash, - scopes: scopesToJson(parsed.data.scopes), + scopes: scopesToJson([...new Set(parsed.data.scopes)]), }); return NextResponse.json({ id, name: parsed.data.name, prefix, key: fullKey }); } + +export async function DELETE(request: Request) { + const env = getEnv(); + const user = await requireUser(env, request); + if (request.headers.get("Origin") !== new URL(request.url).origin) return NextResponse.json({ error: "Invalid origin" }, { status: 403 }); + const id = new URL(request.url).searchParams.get("id"); + if (!id) return NextResponse.json({ error: "Key required" }, { status: 400 }); + const db = getDb(env); + const [key] = await db.select({ id: apiKeys.id, scopes: apiKeys.scopes }).from(apiKeys).where(and(eq(apiKeys.id, id), eq(apiKeys.userId, user.id))).limit(1); + if (!key) return NextResponse.json({ error: "Key not found" }, { status: 404 }); + if (parseScopes(key.scopes).some((scope) => ["domains", "accounts", "mailboxes"].includes(scope))) return NextResponse.json({ error: "Manage this key in Admin API keys" }, { status: 403 }); + await db.update(agentSendApprovals).set({ status: "cancelled" }).where(and(eq(agentSendApprovals.requestKeyId, id), eq(agentSendApprovals.status, "pending"))); + await db.delete(apiKeys).where(and(eq(apiKeys.id, id), eq(apiKeys.userId, user.id))); + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/drafts/[id]/attachments/[attachmentId]/route.ts b/src/app/api/drafts/[id]/attachments/[attachmentId]/route.ts index 10ccdec..bf0e04d 100644 --- a/src/app/api/drafts/[id]/attachments/[attachmentId]/route.ts +++ b/src/app/api/drafts/[id]/attachments/[attachmentId]/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { eq } from "drizzle-orm"; +import { eq, sql } from "drizzle-orm"; import { getEnv } from "@/lib/cloudflare"; import { getDb } from "@/db"; -import { messages } from "@/db/schema"; +import { agentDraftMetadata, messages } from "@/db/schema"; import { requireUser } from "@/lib/auth/cookies"; import { deleteMessageAttachment } from "@/lib/email/attachments"; import { userOwnsDraft } from "../../../utils"; @@ -27,5 +27,6 @@ export async function DELETE(request: Request, { params }: DraftAttachmentRouteP if (!removed) { return NextResponse.json({ error: "Attachment not found" }, { status: 404 }); } + await db.update(agentDraftMetadata).set({ revision: sql`${agentDraftMetadata.revision} + 1`, humanEditedAt: new Date() }).where(eq(agentDraftMetadata.draftId, id)); return NextResponse.json({ ok: true }); } diff --git a/src/app/api/drafts/[id]/route.ts b/src/app/api/drafts/[id]/route.ts index 8e0f0e0..bdc592c 100644 --- a/src/app/api/drafts/[id]/route.ts +++ b/src/app/api/drafts/[id]/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { eq } from "drizzle-orm"; +import { eq, sql } from "drizzle-orm"; import { getEnv } from "@/lib/cloudflare"; import { getDb } from "@/db"; -import { messages } from "@/db/schema"; +import { agentDraftMetadata, messages } from "@/db/schema"; import { requireUser } from "@/lib/auth/cookies"; import { buildSnippet } from "@/lib/email/parse"; import type { DraftPayload, DraftRouteParams } from "./types"; @@ -25,7 +25,8 @@ export async function GET(request: Request, { params }: DraftRouteParams) { } const attachments = await listMessageAttachments(env, id); - return NextResponse.json({ draft: { ...draft, attachments } }); + const [agent] = await db.select().from(agentDraftMetadata).where(eq(agentDraftMetadata.draftId, id)).limit(1); + return NextResponse.json({ draft: { ...draft, attachments, agent: agent ? { revision: agent.revision, origin: agent.origin } : null } }); } export async function PATCH(request: Request, { params }: DraftRouteParams) { @@ -45,6 +46,10 @@ export async function PATCH(request: Request, { params }: DraftRouteParams) { if (!userOwnsDraft(draft, user.id)) { return NextResponse.json({ error: "Draft not found" }, { status: 404 }); } + const [agent] = await db.select({ mailboxId: agentDraftMetadata.mailboxId }).from(agentDraftMetadata).where(eq(agentDraftMetadata.draftId, id)).limit(1); + if (agent && input.mailboxId !== agent.mailboxId) { + return NextResponse.json({ error: "Agent draft mailbox cannot be changed" }, { status: 409 }); + } const sender = await getDraftSender(env, user.id, input); if ("error" in sender) { return NextResponse.json({ error: sender.error }, { status: 403 }); @@ -66,6 +71,7 @@ export async function PATCH(request: Request, { params }: DraftRouteParams) { htmlBody: html || null, }) .where(eq(messages.id, id)); + await db.update(agentDraftMetadata).set({ revision: sql`${agentDraftMetadata.revision} + 1`, humanEditedAt: new Date() }).where(eq(agentDraftMetadata.draftId, id)); return NextResponse.json({ draft: { id } }); } diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts index fff692d..b208cb7 100644 --- a/src/app/api/send/route.ts +++ b/src/app/api/send/route.ts @@ -7,7 +7,7 @@ import { parseSendRequest } from "./utils"; import { RequestBodyTooLargeError } from "@/lib/http/errors"; import { getSendErrorStatus } from "./error-utils"; import { getDb } from "@/db"; -import { messages } from "@/db/schema"; +import { agentDraftMetadata, messages } from "@/db/schema"; import { eq } from "drizzle-orm"; import { loadMessageAttachmentContents } from "@/lib/email/attachments"; import { userOwnsDraft } from "@/app/api/drafts/utils"; @@ -36,6 +36,8 @@ export async function POST(request: Request) { if (!userOwnsDraft(draft, user.id)) { return NextResponse.json({ error: "Draft not found" }, { status: 404 }); } + const [agent] = await db.select({ draftId: agentDraftMetadata.draftId }).from(agentDraftMetadata).where(eq(agentDraftMetadata.draftId, draftId)).limit(1); + if (agent) return NextResponse.json({ error: "Review and confirm this AI draft before sending" }, { status: 409 }); attachments.push(...(await loadMessageAttachmentContents(env, draftId))); } diff --git a/src/app/api/v1/accounts/[id]/route.ts b/src/app/api/v1/accounts/[id]/route.ts new file mode 100644 index 0000000..122400c --- /dev/null +++ b/src/app/api/v1/accounts/[id]/route.ts @@ -0,0 +1 @@ +export { GET, PATCH } from "./utils"; diff --git a/src/app/api/v1/accounts/[id]/types.d.ts b/src/app/api/v1/accounts/[id]/types.d.ts new file mode 100644 index 0000000..bf64584 --- /dev/null +++ b/src/app/api/v1/accounts/[id]/types.d.ts @@ -0,0 +1 @@ +export type AdminAccountRouteParams = { params: Promise<{ id: string }> }; diff --git a/src/app/api/v1/accounts/[id]/utils.ts b/src/app/api/v1/accounts/[id]/utils.ts new file mode 100644 index 0000000..3a51032 --- /dev/null +++ b/src/app/api/v1/accounts/[id]/utils.ts @@ -0,0 +1,50 @@ +import { NextResponse } from "next/server"; +import { eq } from "drizzle-orm"; +import { getEnv } from "@/lib/cloudflare"; +import { getDb } from "@/db"; +import { users } from "@/db/schema"; +import { authenticateAdminApiKey, canManageAdminAccounts } from "@/lib/api/admin-auth"; +import { updateManagedAccountSchema } from "@/lib/validators"; +import { getLicenseEntitlements } from "@/lib/licenses/service"; +import { selectAccountById, updateAccountCredentials } from "@/app/api/accounts/[id]/utils"; +import { deleteUserSessions } from "@/lib/auth/session"; +import type { AdminAccountRouteParams } from "./types"; + +export async function GET(request: Request, { params }: AdminAccountRouteParams) { + const env = getEnv(); + const auth = await authenticateAdminApiKey(env, request, "accounts"); + if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + if (!(await canManageAdminAccounts(env))) return NextResponse.json({ error: "A Team license is required to manage accounts" }, { status: 403 }); + const { id } = await params; + const account = await selectAccountById(getDb(env), id); + if (!account || (account.id !== auth.userId && account.createdByUserId !== auth.userId)) return NextResponse.json({ error: "Account not found" }, { status: 404 }); + return NextResponse.json({ account: { + id: account.id, email: account.email, name: account.name, role: account.role, + disabled: account.disabled, canManageMailboxes: account.canManageMailboxes, + forwardingEmail: account.forwardingEmail, hasAvatar: !!account.avatarKey, + } }); +} + +export async function PATCH(request: Request, { params }: AdminAccountRouteParams) { + const env = getEnv(); + const auth = await authenticateAdminApiKey(env, request, "accounts"); + if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + if (!(await canManageAdminAccounts(env))) return NextResponse.json({ error: "A Team license is required to manage accounts" }, { status: 403 }); + const { id } = await params; + const db = getDb(env); + const account = await selectAccountById(db, id); + if (!account || (account.id !== auth.userId && account.createdByUserId !== auth.userId)) return NextResponse.json({ error: "Account not found" }, { status: 404 }); + const parsed = updateManagedAccountSchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); + const canForwardEmail = (await getLicenseEntitlements(env)).canForwardEmail; + if (!canForwardEmail && parsed.data.forwardingEmail && parsed.data.forwardingEmail !== account.forwardingEmail) return NextResponse.json({ error: "A Pro or Team license is required for email forwarding" }, { status: 403 }); + await updateAccountCredentials(db, id, { name: parsed.data.name, password: parsed.data.password ?? null }); + if (parsed.data.password) await deleteUserSessions(env, id); + await db.update(users).set({ + role: parsed.data.role, + disabled: parsed.data.disabled, + canManageMailboxes: parsed.data.canManageMailboxes, + ...(parsed.data.forwardingEmail !== undefined ? { forwardingEmail: parsed.data.forwardingEmail } : {}), + }).where(eq(users.id, id)); + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/v1/accounts/route.ts b/src/app/api/v1/accounts/route.ts new file mode 100644 index 0000000..a595d36 --- /dev/null +++ b/src/app/api/v1/accounts/route.ts @@ -0,0 +1 @@ +export { GET, POST } from "./utils"; diff --git a/src/app/api/v1/accounts/utils.ts b/src/app/api/v1/accounts/utils.ts new file mode 100644 index 0000000..99db8c7 --- /dev/null +++ b/src/app/api/v1/accounts/utils.ts @@ -0,0 +1,56 @@ +import { NextResponse } from "next/server"; +import { eq, or } from "drizzle-orm"; +import { getEnv } from "@/lib/cloudflare"; +import { getDb } from "@/db"; +import { mailboxes, users } from "@/db/schema"; +import { authenticateAdminApiKey, canManageAdminAccounts } from "@/lib/api/admin-auth"; +import { hashPassword } from "@/lib/auth/password"; +import { newId } from "@/lib/ids"; +import { createUserAccountSchema } from "@/lib/validators"; +import { ensureEmailRoutingRuleToWorker } from "@/lib/cloudflare-api"; +import { ensureMailboxDomainRouting } from "@/lib/mailboxes/domain-addresses"; +import { accountListItemFromUser, getDomainForAdmin, getExistingMailbox } from "@/app/api/accounts/utils"; + +export async function GET(request: Request) { + const env = getEnv(); + const auth = await authenticateAdminApiKey(env, request, "accounts"); + if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + if (!(await canManageAdminAccounts(env))) return NextResponse.json({ error: "A Team license is required to manage accounts" }, { status: 403 }); + const rows = await getDb(env).select().from(users).where(or(eq(users.id, auth.userId), eq(users.createdByUserId, auth.userId))); + return NextResponse.json({ accounts: rows.map(accountListItemFromUser) }); +} + +export async function POST(request: Request) { + const env = getEnv(); + const auth = await authenticateAdminApiKey(env, request, "accounts"); + if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + if (!(await canManageAdminAccounts(env))) return NextResponse.json({ error: "A Team license is required to manage accounts" }, { status: 403 }); + const parsed = createUserAccountSchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); + const db = getDb(env); + const domain = await getDomainForAdmin(db, auth.userId, parsed.data.domainId); + if (!domain) return NextResponse.json({ error: "Domain not found" }, { status: 404 }); + const username = parsed.data.username.toLowerCase().trim(); + const email = `${username}@${domain.hostname}`; + const [existing] = await db.select({ id: users.id }).from(users).where(eq(users.email, email)).limit(1); + if (existing || await getExistingMailbox(db, domain.id, username)) return NextResponse.json({ error: "Email address is already assigned" }, { status: 409 }); + const userId = newId("usr"); + try { + await ensureEmailRoutingRuleToWorker(env, domain.zoneId, email); + const [account] = await db.insert(users).values({ + id: userId, + email, + passwordHash: hashPassword(parsed.data.password), + name: username, + role: parsed.data.role, + createdByUserId: auth.userId, + }).returning(); + const mailboxId = newId("mbx"); + await db.insert(mailboxes).values({ id: mailboxId, userId, domainId: domain.id, localPart: username, displayName: username }); + await ensureMailboxDomainRouting(env, db, { id: mailboxId, domainId: domain.id, localPart: username, useAllDomains: true }); + return NextResponse.json({ account: accountListItemFromUser(account) }, { status: 201 }); + } catch (error) { + await db.delete(users).where(eq(users.id, userId)); + return NextResponse.json({ error: error instanceof Error ? error.message : "Failed to create account mailbox" }, { status: 502 }); + } +} diff --git a/src/app/api/v1/domains/[id]/dns/route.ts b/src/app/api/v1/domains/[id]/dns/route.ts index 75b1916..cf0fb15 100644 --- a/src/app/api/v1/domains/[id]/dns/route.ts +++ b/src/app/api/v1/domains/[id]/dns/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { getEnv } from "@/lib/cloudflare"; -import { authenticateApiKey, requireScope } from "@/lib/api/auth"; +import { authenticateAdminApiKey } from "@/lib/api/admin-auth"; import { getDomainForUser } from "@/lib/domains/service"; import { getDomainDnsView } from "@/lib/domains/dns-view"; @@ -8,8 +8,8 @@ type Params = { params: Promise<{ id: string }> }; export async function GET(request: Request, { params }: Params) { const env = getEnv(); - const auth = await authenticateApiKey(env, request.headers.get("authorization")); - if (!auth || !requireScope(auth.scopes, "domains")) { + const auth = await authenticateAdminApiKey(env, request, "domains"); + if (!auth) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/src/app/api/v1/domains/[id]/dns/setup/route.ts b/src/app/api/v1/domains/[id]/dns/setup/route.ts index 7ce3b71..4d2419b 100644 --- a/src/app/api/v1/domains/[id]/dns/setup/route.ts +++ b/src/app/api/v1/domains/[id]/dns/setup/route.ts @@ -3,7 +3,7 @@ import { eq } from "drizzle-orm"; import { getEnv } from "@/lib/cloudflare"; import { getDb } from "@/db"; import { domains } from "@/db/schema"; -import { authenticateApiKey, requireScope } from "@/lib/api/auth"; +import { authenticateAdminApiKey } from "@/lib/api/admin-auth"; import { getDomainForUser } from "@/lib/domains/service"; import { getDomainDnsView } from "@/lib/domains/dns-view"; import type { DnsAuthRecord } from "@/lib/domains/dns-audit"; @@ -15,8 +15,8 @@ const DNS_RECORDS: DnsAuthRecord[] = ["mx", "spf", "dkim", "dmarc"]; export async function POST(request: Request, { params }: Params) { const env = getEnv(); - const auth = await authenticateApiKey(env, request.headers.get("authorization")); - if (!auth || !requireScope(auth.scopes, "domains")) { + const auth = await authenticateAdminApiKey(env, request, "domains"); + if (!auth) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/src/app/api/v1/domains/[id]/route.ts b/src/app/api/v1/domains/[id]/route.ts index a8c4bd4..0b056ab 100644 --- a/src/app/api/v1/domains/[id]/route.ts +++ b/src/app/api/v1/domains/[id]/route.ts @@ -1,14 +1,14 @@ import { NextResponse } from "next/server"; import { getEnv } from "@/lib/cloudflare"; -import { authenticateApiKey, requireScope } from "@/lib/api/auth"; +import { authenticateAdminApiKey } from "@/lib/api/admin-auth"; import { getDomainForUser, removeDomainForUser } from "@/lib/domains/service"; type Params = { params: Promise<{ id: string }> }; async function authorize(request: Request) { const env = getEnv(); - const auth = await authenticateApiKey(env, request.headers.get("authorization")); - if (!auth || !requireScope(auth.scopes, "domains")) return null; + const auth = await authenticateAdminApiKey(env, request, "domains"); + if (!auth) return null; return auth; } diff --git a/src/app/api/v1/domains/route.ts b/src/app/api/v1/domains/route.ts index 7cd1d25..4a99fec 100644 --- a/src/app/api/v1/domains/route.ts +++ b/src/app/api/v1/domains/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { getEnv } from "@/lib/cloudflare"; -import { authenticateApiKey, requireScope } from "@/lib/api/auth"; +import { authenticateAdminApiKey } from "@/lib/api/admin-auth"; import { addDomainSchema } from "@/lib/validators"; import { addDomainForUser, listUserDomains } from "@/lib/domains/service"; import { getDomainDnsView, summariseDomainDns } from "@/lib/domains/dns-view"; @@ -13,8 +13,8 @@ function ownerId(user: { id: string; canManageMailboxes: boolean; createdByUserI export async function GET(request: Request) { const env = getEnv(); - const auth = await authenticateApiKey(env, request.headers.get("authorization")); - if (!auth || !requireScope(auth.scopes, "domains")) { + const auth = await authenticateAdminApiKey(env, request, "domains"); + if (!auth) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } @@ -36,8 +36,8 @@ export async function GET(request: Request) { export async function POST(request: Request) { const env = getEnv(); - const auth = await authenticateApiKey(env, request.headers.get("authorization")); - if (!auth || !requireScope(auth.scopes, "domains")) { + const auth = await authenticateAdminApiKey(env, request, "domains"); + if (!auth) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/src/app/api/v1/mailboxes/[id]/route.ts b/src/app/api/v1/mailboxes/[id]/route.ts new file mode 100644 index 0000000..cbd063b --- /dev/null +++ b/src/app/api/v1/mailboxes/[id]/route.ts @@ -0,0 +1 @@ +export { GET, PATCH, DELETE } from "./utils"; diff --git a/src/app/api/v1/mailboxes/[id]/types.d.ts b/src/app/api/v1/mailboxes/[id]/types.d.ts new file mode 100644 index 0000000..0964248 --- /dev/null +++ b/src/app/api/v1/mailboxes/[id]/types.d.ts @@ -0,0 +1 @@ +export type AdminMailboxRouteParams = { params: Promise<{ id: string }> }; diff --git a/src/app/api/v1/mailboxes/[id]/utils.ts b/src/app/api/v1/mailboxes/[id]/utils.ts new file mode 100644 index 0000000..2fee303 --- /dev/null +++ b/src/app/api/v1/mailboxes/[id]/utils.ts @@ -0,0 +1,62 @@ +import { NextResponse } from "next/server"; +import { and, eq, or } from "drizzle-orm"; +import { getEnv } from "@/lib/cloudflare"; +import { getDb } from "@/db"; +import { mailboxes, users } from "@/db/schema"; +import { authenticateAdminApiKey } from "@/lib/api/admin-auth"; +import { updateMailboxSchema } from "@/lib/validators"; +import { getMailboxUpdateValues } from "@/app/api/mailboxes/[id]/utils"; +import { ensureMailboxDomainRouting, removeMailboxDomainRouting } from "@/lib/mailboxes/domain-addresses"; +import type { AdminMailboxRouteParams } from "./types"; + +async function managedMailbox(request: Request, id: string) { + const env = getEnv(); + const auth = await authenticateAdminApiKey(env, request, "mailboxes"); + if (!auth) return { env, mailbox: null, error: NextResponse.json({ error: "Unauthorized" }, { status: 401 }) }; + const [mailbox] = await getDb(env).select({ + id: mailboxes.id, userId: mailboxes.userId, domainId: mailboxes.domainId, + localPart: mailboxes.localPart, displayName: mailboxes.displayName, + type: mailboxes.type, disabled: mailboxes.disabled, + useAllDomains: mailboxes.useAllDomains, createdAt: mailboxes.createdAt, + }).from(mailboxes).innerJoin(users, eq(mailboxes.userId, users.id)) + .where(and(eq(mailboxes.id, id), or(eq(users.id, auth.userId), eq(users.createdByUserId, auth.userId)))).limit(1); + if (!mailbox) return { env, mailbox: null, error: NextResponse.json({ error: "Mailbox not found" }, { status: 404 }) }; + return { env, mailbox, error: null }; +} + +export async function GET(request: Request, { params }: AdminMailboxRouteParams) { + const access = await managedMailbox(request, (await params).id); + if (access.error) return access.error; + return NextResponse.json({ mailbox: access.mailbox }); +} + +export async function PATCH(request: Request, { params }: AdminMailboxRouteParams) { + const access = await managedMailbox(request, (await params).id); + if (access.error) return access.error; + const parsed = updateMailboxSchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); + const db = getDb(access.env); + if (parsed.data.useAllDomains === true) { + try { + await ensureMailboxDomainRouting(access.env, db, { id: access.mailbox!.id, domainId: access.mailbox!.domainId, localPart: access.mailbox!.localPart, useAllDomains: true }); + } catch (error) { + return NextResponse.json({ error: error instanceof Error ? error.message : "Failed to configure inbound routing" }, { status: 502 }); + } + } + const values = getMailboxUpdateValues(parsed.data); + if (Object.keys(values).length) await db.update(mailboxes).set(values).where(eq(mailboxes.id, access.mailbox!.id)); + return NextResponse.json({ ok: true }); +} + +export async function DELETE(request: Request, { params }: AdminMailboxRouteParams) { + const access = await managedMailbox(request, (await params).id); + if (access.error) return access.error; + const db = getDb(access.env); + try { + await removeMailboxDomainRouting(access.env, db, access.mailbox!); + } catch (error) { + return NextResponse.json({ error: error instanceof Error ? error.message : "Failed to remove Cloudflare routing rule" }, { status: 502 }); + } + await db.delete(mailboxes).where(eq(mailboxes.id, access.mailbox!.id)); + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/v1/mailboxes/route.ts b/src/app/api/v1/mailboxes/route.ts new file mode 100644 index 0000000..a595d36 --- /dev/null +++ b/src/app/api/v1/mailboxes/route.ts @@ -0,0 +1 @@ +export { GET, POST } from "./utils"; diff --git a/src/app/api/v1/mailboxes/utils.ts b/src/app/api/v1/mailboxes/utils.ts new file mode 100644 index 0000000..25f8642 --- /dev/null +++ b/src/app/api/v1/mailboxes/utils.ts @@ -0,0 +1,55 @@ +import { NextResponse } from "next/server"; +import { and, eq, or } from "drizzle-orm"; +import { getEnv } from "@/lib/cloudflare"; +import { getDb } from "@/db"; +import { domains, mailboxAliases, mailboxes, users } from "@/db/schema"; +import { authenticateAdminApiKey, canManageAdminAccounts } from "@/lib/api/admin-auth"; +import { newId } from "@/lib/ids"; +import { mailboxSchema } from "@/lib/validators"; +import { ensureMailboxDomainRouting } from "@/lib/mailboxes/domain-addresses"; + +export async function GET(request: Request) { + const env = getEnv(); + const auth = await authenticateAdminApiKey(env, request, "mailboxes"); + if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + const rows = await getDb(env).select({ + id: mailboxes.id, userId: mailboxes.userId, domainId: mailboxes.domainId, + localPart: mailboxes.localPart, hostname: domains.hostname, + displayName: mailboxes.displayName, type: mailboxes.type, + disabled: mailboxes.disabled, createdAt: mailboxes.createdAt, + }).from(mailboxes).innerJoin(users, eq(mailboxes.userId, users.id)).innerJoin(domains, eq(mailboxes.domainId, domains.id)) + .where(or(eq(users.id, auth.userId), eq(users.createdByUserId, auth.userId))); + return NextResponse.json({ mailboxes: rows }); +} + +export async function POST(request: Request) { + const env = getEnv(); + const auth = await authenticateAdminApiKey(env, request, "mailboxes"); + if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + const parsed = mailboxSchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); + const db = getDb(env); + const mailboxType = parsed.data.type ?? "personal"; + if (mailboxType === "shared" && !(await canManageAdminAccounts(env))) return NextResponse.json({ error: "A Team license is required to create shared inboxes" }, { status: 403 }); + const ownerUserId = mailboxType === "shared" ? auth.userId : parsed.data.ownerUserId ?? auth.userId; + if (ownerUserId !== auth.userId) { + const [owner] = await db.select({ id: users.id }).from(users).where(and(eq(users.id, ownerUserId), eq(users.createdByUserId, auth.userId))).limit(1); + if (!owner) return NextResponse.json({ error: "Account not found" }, { status: 404 }); + } + const [domain] = await db.select().from(domains).where(and(eq(domains.id, parsed.data.domainId), eq(domains.userId, auth.userId))).limit(1); + if (!domain) return NextResponse.json({ error: "Domain not found" }, { status: 404 }); + const localPart = parsed.data.localPart.toLowerCase(); + const [existing] = await db.select({ id: mailboxes.id }).from(mailboxes).where(and(eq(mailboxes.domainId, domain.id), eq(mailboxes.localPart, localPart))).limit(1); + if (existing) return NextResponse.json({ error: "Mailbox already exists" }, { status: 409 }); + const [alias] = await db.select({ id: mailboxAliases.id }).from(mailboxAliases).where(and(eq(mailboxAliases.domainId, domain.id), eq(mailboxAliases.localPart, localPart))).limit(1); + if (alias) return NextResponse.json({ error: "An alias already uses this address" }, { status: 409 }); + const id = newId("mbx"); + await db.insert(mailboxes).values({ id, userId: ownerUserId, domainId: domain.id, localPart, displayName: parsed.data.displayName, type: mailboxType }); + try { + await ensureMailboxDomainRouting(env, db, { id, domainId: domain.id, localPart, useAllDomains: true }); + } catch (error) { + await db.delete(mailboxes).where(eq(mailboxes.id, id)); + return NextResponse.json({ error: error instanceof Error ? error.message : "Failed to create Cloudflare routing rule" }, { status: 502 }); + } + return NextResponse.json({ id, address: `${localPart}@${domain.hostname}`, type: mailboxType }, { status: 201 }); +} diff --git a/src/app/globals.css b/src/app/globals.css index 440647c..e8df085 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -170,3 +170,7 @@ pre { .email-quote-content { display: flow-root; } + +button { + cursor: pointer; +} \ No newline at end of file diff --git a/src/app/mcp/route.ts b/src/app/mcp/route.ts new file mode 100644 index 0000000..a84b49b --- /dev/null +++ b/src/app/mcp/route.ts @@ -0,0 +1,22 @@ +import { getEnv } from "@/lib/cloudflare"; +import { authenticateMcpRequest } from "@/lib/mcp/auth"; +import { createMailflareMcpHandler } from "@/lib/mcp/server"; +import { allowAgentRequest } from "@/lib/agent/rate-limit"; + +async function handle(request: Request) { + const url = new URL(request.url); + const origin = request.headers.get("Origin"); + if (origin && origin !== url.origin) return new Response("Invalid origin", { status: 403 }); + const env = getEnv(); + if (env.APP_URL && url.host !== new URL(env.APP_URL).host) return new Response("Invalid host", { status: 403 }); + const principal = await authenticateMcpRequest(env, request); + if (!principal) return new Response("Unauthorized", { status: 401, headers: { "WWW-Authenticate": "Bearer" } }); + if (!await allowAgentRequest(env, `mcp:${principal.keyId}`)) return new Response("Rate limit exceeded", { status: 429 }); + const handler = createMailflareMcpHandler(env, principal, url.origin, request.headers.get("Authorization") ?? ""); + try { return await handler.fetch(request); } + finally { await handler.close(); } +} + +export const POST = handle; +export const GET = handle; +export const DELETE = handle; diff --git a/src/components/agent/agent-panel.tsx b/src/components/agent/agent-panel.tsx new file mode 100644 index 0000000..cbb3d40 --- /dev/null +++ b/src/components/agent/agent-panel.tsx @@ -0,0 +1,277 @@ +"use client"; + +import "./style.scss" + +import { useCallback, useEffect, useRef, useState } from "react"; +import { usePathname } from "next/navigation"; +import { FileText, ListChecks, Maximize2, Minimize2, PenLine, Plus, Send, Settings2, Sparkles, Square, X } from "lucide-react"; +import { authFetch } from "@/lib/auth/client"; +import { useSelectedMailbox } from "@/components/mailbox-provider"; +import { useCompose } from "@/components/compose/compose-context"; +import { Button } from "@/components/ui/button"; +import { AgentTurnView } from "./agent-turn"; +import { SendReview } from "./send-review"; +import type { ReviewSnapshot } from "./send-review-types"; +import { approveAgentAction, requestDraftReview } from "./client-actions"; +import type { AgentConversation, AgentConversationsResponse, AgentErrorResponse, AgentEvent, AgentHistoryResponse, AgentJob, AgentJobsResponse, AgentMessage, AgentPanelProps, AgentPanelView, AgentProvider, AgentSettings, AgentSettingsResponse } from "./types"; +import { appendAgentReasoning, consumeAgentStream, groupAgentMessages, markAgentDraftSent, normalizeAgentHistory, readAgentConversationId, resizeAgentInput, saveAgentConversationId, shouldSubmitAgentInput } from "./utils"; + +export function AgentPanel({ open, fullSize, onClose, onToggleFullSize }: AgentPanelProps) { + const { selectedMailbox } = useSelectedMailbox(); + const pathname = usePathname(); + const { openDraftComposer } = useCompose(); + const [view, setView] = useState("chat"); + const [settings, setSettings] = useState(null); + const [reviewers, setReviewers] = useState<{ id: string; name: string; email: string }[]>([]); + const [canManage, setCanManage] = useState(false); + const [canConfigureProvider, setCanConfigureProvider] = useState(false); + const [providerConfigured, setProviderConfigured] = useState(false); + const [autoReplyEnabled, setAutoReplyEnabled] = useState(false); + const [conversationId, setConversationId] = useState(null); + const [conversations, setConversations] = useState([]); + const [messages, setMessages] = useState([]); + const [jobs, setJobs] = useState([]); + const [provider, setProvider] = useState(null); + const [input, setInput] = useState(""); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [approvingId, setApprovingId] = useState(null); + const [draftReview, setDraftReview] = useState<{ approvalId: string; snapshot: ReviewSnapshot; draftId: string } | null>(null); + const abort = useRef(null); + const menuRef = useRef(null); + const inputRef = useRef(null); + const selectedConversationRef = useRef(null); + const mailboxId = selectedMailbox?.id; + const selectedMessageId = pathname.match(/^\/(?:inbox|sent|archived|spam|trash|starred|snoozed|folders\/[^/]+)\/([^/]+)/)?.[1] ?? null; + const welcomePrompts = selectedMessageId ? [ + { label: "Summarize this email", detail: "Get the key points", prompt: `Read the thread containing email ${selectedMessageId} and summarize its key points.`, icon: FileText }, + { label: "Suggest a reply", detail: "Create a draft for review", prompt: `Read the thread containing email ${selectedMessageId} and draft a reply.`, icon: PenLine }, + { label: "List action items", detail: "Find next steps in this email", prompt: `Read the thread containing email ${selectedMessageId} and list the action items.`, icon: ListChecks }, + ] : [ + { label: "Summarize recent mail", detail: "Catch up on your inbox", prompt: "Summarize my recent email in this mailbox.", icon: FileText }, + { label: "Suggest a reply", detail: "Create a draft for review", prompt: "Read my latest email and draft a reply.", icon: PenLine }, + { label: "Find action items", detail: "See what needs attention", prompt: "Find action items in my recent email.", icon: ListChecks }, + ]; + + const refresh = useCallback(async () => { + if (!mailboxId) return; + const [settingsResponse, conversationsResponse, jobsResponse] = await Promise.all([ + authFetch(`/api/agent/settings?mailboxId=${encodeURIComponent(mailboxId)}`), + authFetch(`/api/agent/conversations?mailboxId=${encodeURIComponent(mailboxId)}`), + authFetch(`/api/agent/jobs?mailboxId=${encodeURIComponent(mailboxId)}`), + ]); + if (!settingsResponse.ok) { + const data = await settingsResponse.json().catch(() => ({})) as AgentErrorResponse; + setError(data.error || "Could not load assistant settings"); + return; + } + if (settingsResponse.ok) { + const data = await settingsResponse.json() as AgentSettingsResponse; + setSettings(data.settings); + setCanManage(data.canManage); + setCanConfigureProvider(data.canConfigureProvider); + setProviderConfigured(data.providerConfigured); + setProvider(data.provider ?? null); + setAutoReplyEnabled(data.autoReplyEnabled); + setReviewers(data.reviewers ?? []); + } + if (conversationsResponse.ok) setConversations(((await conversationsResponse.json()) as AgentConversationsResponse).conversations ?? []); + if (jobsResponse.ok) setJobs(((await jobsResponse.json()) as AgentJobsResponse).jobs ?? []); + }, [mailboxId]); + + useEffect(() => { + abort.current?.abort(); + if (!open || !mailboxId) return; + let cancelled = false; + setConversationId(null); + setMessages([]); + setSettings(null); + setView("chat"); + if (menuRef.current) menuRef.current.open = false; + setError(null); + void refresh().catch(() => { if (!cancelled) setError("Could not load assistant settings"); }); + const savedId = readAgentConversationId(mailboxId); + selectedConversationRef.current = savedId; + if (savedId) void authFetch(`/api/agent/conversations/${encodeURIComponent(savedId)}?mailboxId=${encodeURIComponent(mailboxId)}`).then(async (response) => { + if (cancelled || selectedConversationRef.current !== savedId) return; + if (!response.ok) { saveAgentConversationId(mailboxId, null); selectedConversationRef.current = null; return; } + const history = await response.json() as AgentHistoryResponse; + if (cancelled || selectedConversationRef.current !== savedId) return; + setConversationId(savedId); + setMessages(normalizeAgentHistory(history.messages ?? [])); + }).catch(() => undefined); + return () => { cancelled = true; abort.current?.abort(); }; + }, [mailboxId, open, refresh]); + + useEffect(() => { + const closeMenu = (event: PointerEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) menuRef.current.open = false; + }; + const closeOnEscape = (event: KeyboardEvent) => { + if (event.key === "Escape" && menuRef.current?.open) menuRef.current.open = false; + }; + document.addEventListener("pointerdown", closeMenu); + document.addEventListener("keydown", closeOnEscape); + return () => { + document.removeEventListener("pointerdown", closeMenu); + document.removeEventListener("keydown", closeOnEscape); + }; + }, []); + + useEffect(() => { resizeAgentInput(inputRef.current); }, [input, view]); + + useEffect(() => { + function onDraft(event: Event) { + const data = (event as CustomEvent<{ mailboxId: string }>).detail; + if (open && data.mailboxId === mailboxId) void refresh(); + } + window.addEventListener("mailflare:agent-draft", onDraft); + return () => window.removeEventListener("mailflare:agent-draft", onDraft); + }, [mailboxId, open, refresh]); + + useEffect(() => { + if (!open) return; + const timer = window.setInterval(() => void refresh(), 30_000); + return () => window.clearInterval(timer); + }, [open, refresh]); + + async function selectConversation(id: string) { + if (!mailboxId) return; + abort.current?.abort(); + selectedConversationRef.current = id; + saveAgentConversationId(mailboxId, id); + setConversationId(id); + setView("chat"); + if (menuRef.current) menuRef.current.open = false; + const response = await authFetch(`/api/agent/conversations/${id}?mailboxId=${encodeURIComponent(mailboxId)}`); + if (response.ok) setMessages(normalizeAgentHistory(((await response.json()) as AgentHistoryResponse).messages ?? [])); + } + + async function deleteConversation() { + if (!conversationId) return; + const response = await authFetch(`/api/agent/conversations/${conversationId}`, { method: "DELETE" }); + if (response.ok) { selectedConversationRef.current = null; if (mailboxId) saveAgentConversationId(mailboxId, null); setConversationId(null); setMessages([]); await refresh(); } + if (menuRef.current) menuRef.current.open = false; + } + + async function send(text: string) { + if (!mailboxId || !text.trim() || busy) return; + const controller = new AbortController(); + const userMessageId = crypto.randomUUID(); + const assistantMessageId = crypto.randomUUID(); + const startedAt = Date.now(); + let accepted = false; + abort.current = controller; + setBusy(true); + setError(null); + setInput(""); + setMessages((current) => [...current, { id: userMessageId, role: "user", content: text, createdAt: new Date(startedAt).toISOString() }, { id: assistantMessageId, role: "assistant", content: "", pending: true }]); + try { + const response = await authFetch("/api/agent/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mailboxId, ...(conversationId ? { conversationId } : {}), text }), signal: controller.signal }); + if (!response.ok) throw new Error(((await response.json()) as AgentErrorResponse).error || "Assistant unavailable"); + accepted = true; + await consumeAgentStream(response, (event: AgentEvent) => { + if (event.type === "conversation") { selectedConversationRef.current = event.conversationId; saveAgentConversationId(mailboxId, event.conversationId); setConversationId(event.conversationId); } + if (event.type === "text") setMessages((current) => current.map((item) => item.id === assistantMessageId ? { ...item, content: item.content + event.text } : item)); + if (event.type === "reasoning") setMessages((current) => appendAgentReasoning(current, assistantMessageId, event.text)); + if (event.type === "reclassify") setMessages((current) => appendAgentReasoning(current, assistantMessageId, event.text, true)); + if (event.type === "tool") { + const toolId = `${assistantMessageId}:tool:${event.id}`; + const content = event.state === "running" ? "" : typeof event.result === "string" ? event.result : JSON.stringify(event.result) ?? ""; + const toolState = event.state === "running" ? "running" : event.state === "failed" ? "failed" : "used"; + setMessages((current) => current.some((item) => item.id === toolId) ? current.map((item) => item.id === toolId ? { ...item, toolState, content, recordId: event.recordId } : item) : [...current, { id: toolId, role: "tool", toolName: event.name, toolState, content, recordId: event.recordId }]); + } + if (event.type === "error") setError(event.message); + }); + setMessages((current) => current.map((item) => item.id === assistantMessageId ? { ...item, pending: false, durationMs: Date.now() - startedAt, createdAt: new Date().toISOString() } : item)); + void refresh(); + } catch (cause) { + if (!accepted) { + setMessages((current) => current.filter((item) => item.id !== userMessageId && item.id !== assistantMessageId)); + if (!controller.signal.aborted) setInput(text); + } else setMessages((current) => current.map((item) => item.id === assistantMessageId ? { ...item, pending: false, durationMs: Date.now() - startedAt, createdAt: new Date().toISOString() } : item)); + if (!controller.signal.aborted) setError(cause instanceof Error ? cause.message : "Assistant failed"); + } finally { setBusy(false); } + } + + async function saveSettings() { + if (!settings) return; + setBusy(true); + setError(null); + try { + const response = await authFetch("/api/agent/settings", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(settings) }); + if (!response.ok) throw new Error(((await response.json()) as AgentErrorResponse).error || "Could not save settings"); + await refresh(); + } catch (cause) { setError(cause instanceof Error ? cause.message : "Could not save settings"); } + finally { setBusy(false); } + } + + async function retryJob(id: string) { + const response = await authFetch(`/api/agent/jobs/${id}/retry`, { method: "POST" }); + if (response.ok) await refresh(); + else setError(((await response.json()) as AgentErrorResponse).error || "Could not retry draft"); + } + + async function discardJobDraft(draftId: string) { + const response = await authFetch(`/api/drafts/${encodeURIComponent(draftId)}`, { method: "DELETE" }); + if (response.ok) await refresh(); + else setError(((await response.json()) as AgentErrorResponse).error || "Could not discard draft"); + } + + async function startDraftReview(draftId: string, revision: number) { + setApprovingId(draftId); + setError(null); + try { setDraftReview({ ...await requestDraftReview(draftId, revision), draftId }); } + catch (cause) { setError(cause instanceof Error ? cause.message : "Could not open draft review"); } + finally { setApprovingId(null); } + } + + async function confirmAction(item: AgentMessage) { + if (!item.recordId) return; + setApprovingId(item.id); + setError(null); + try { + const result = await approveAgentAction(item.recordId); + setMessages((current) => current.map((message) => message.id === item.id ? { ...message, content: JSON.stringify(result) } : message)); + window.dispatchEvent(new Event("mailflare:messages-changed")); + } catch (cause) { setError(cause instanceof Error ? cause.message : "Could not approve action"); } + finally { setApprovingId(null); } + } + + return
+
+
+

Previous chats

{conversations.length ? conversations.map((item) => ) :

No previous chats

}
{conversationId && }
+
+ {error &&

{error}

} + {view === "chat" && <> +
+
+ {settings && !settings.enabled &&

The assistant is disabled for this mailbox. Open settings to enable it.

} + {settings?.enabled && !providerConfigured &&

Configure an AI provider to use chat and auto-drafts.

} + {messages.length === 0 && settings?.enabled &&
{welcomePrompts.map((item) => { const Icon = item.icon; return ; })}
} + {groupAgentMessages(messages).map((turn) => void startDraftReview(draftId, revision)} onApproveAction={(item) => void confirmAction(item)} approvingId={approvingId} />)} + {jobs.filter((job) => job.status === "completed" && job.draftId).slice(0, 5).map((job) =>

Auto-draft ready

)} +
+
+
{ event.preventDefault(); void send(input); }}>