diff --git a/docs/.nojekyll b/docs/.nojekyll
new file mode 100644
index 0000000..e69de29
diff --git a/docs/agent-tutorial.html b/docs/agent-tutorial.html
new file mode 100644
index 0000000..846e0ed
--- /dev/null
+++ b/docs/agent-tutorial.html
@@ -0,0 +1,231 @@
+
+
+
+
+
+Agent Tutorial — SquareMCP Docs
+
+
+
+
+
+
+
+
+
+
+
Build a LinkedIn posting agent
+
Real code: a Claude agent that researches a topic, drafts a post, and publishes it to LinkedIn — fully automated.
+
+
+
+ What you'll build
+ A Node.js script that (1) takes a topic from the command line, (2) uses Claude to research and draft a LinkedIn post, (3) calls the SquareMCP linkedin_create_post tool to publish — all in one agentic loop.
+
+
+
Prerequisites
+
+
+
Step 1 — Install dependencies
+
npm init -y
+npm install @anthropic-ai/sdk @modelcontextprotocol/sdk
+
+
Step 2 — Create the agent
+
+
Create linkedin-agent.mjs:
+
+
import Anthropic from '@anthropic-ai/sdk' ;
+import { Client } from '@modelcontextprotocol/sdk/client/index.js' ;
+import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' ;
+
+const SQUAREMCP_URL = 'https://hermes.squaremcp.com/mcp' ;
+const SQUAREMCP_TOKEN = process.env.SQUAREMCP_TOKEN ; // your Bearer token
+const topic = process.argv[2 ] ?? 'AI trends in 2026' ;
+
+// ── 1. Connect to SquareMCP ──────────────────────────────────────
+const transport = new StreamableHTTPClientTransport (new URL (SQUAREMCP_URL), {
+ requestInit: { headers: { Authorization: `Bearer ${ SQUAREMCP_TOKEN} ` } },
+});
+
+const mcpClient = new Client ({ name: 'linkedin-agent' , version: '1.0.0' });
+await mcpClient.connect(transport);
+
+// Fetch available tools from SquareMCP
+const { tools: mcpTools } = await mcpClient.listTools ();
+
+// Convert MCP tool descriptors to Anthropic tool format
+const anthropicTools = mcpTools.map (t => ({
+ name: t.name,
+ description: t.description,
+ input_schema: t.inputSchema,
+}));
+
+// ── 2. Run the agentic loop ──────────────────────────────────────
+const anthropic = new Anthropic ();
+const messages = [
+ {
+ role: 'user' ,
+ content: `You are a LinkedIn content strategist. Your job:
+1. Think about the topic: "${ topic} "
+2. Draft a compelling LinkedIn post (150-250 words, professional tone, 3-5 hashtags)
+3. Call linkedin_create_post to publish it
+4. Report back what was posted.
+
+Write the post now and publish it.` ,
+ },
+];
+
+console.log (`\n🤖 Agent starting — topic: "${ topic} "\n` );
+
+while (true ) {
+ const response = await anthropic.messages.create ({
+ model: 'claude-opus-4-7' ,
+ max_tokens: 1024 ,
+ tools: anthropicTools,
+ messages,
+ });
+
+ // Append assistant turn
+ messages.push ({ role: 'assistant' , content: response.content });
+
+ if (response.stop_reason === 'end_turn' ) {
+ const text = response.content
+ .filter (b => b.type === 'text' )
+ .map (b => b.text)
+ .join ('\n' );
+ console.log ('\n✅ Agent finished:\n' , text);
+ break ;
+ }
+
+ if (response.stop_reason !== 'tool_use' ) break ;
+
+ // Execute each tool call against SquareMCP
+ const toolResults = [];
+ for (const block of response.content) {
+ if (block.type !== 'tool_use' ) continue ;
+
+ console.log (`🔧 Calling ${ block.name} ...` );
+ let result;
+ try {
+ result = await mcpClient.callTool ({ name: block.name, arguments: block.input });
+ console.log (` ✓ ${ JSON.stringify (result.content[0 ]).slice (0 , 120 )} ...` );
+ } catch (err) {
+ result = { content: [{ type: 'text' , text: `Error: ${ err.message} ` }] };
+ console.error (` ✗ ${ err.message} ` );
+ }
+
+ toolResults.push ({
+ type: 'tool_result' ,
+ tool_use_id: block.id,
+ content: result.content,
+ });
+ }
+
+ messages.push ({ role: 'user' , content: toolResults });
+}
+
+await mcpClient.close ();
+
+
Step 3 — Run it
+
+
export ANTHROPIC_API_KEY=sk-ant-...
+export SQUAREMCP_TOKEN=your-bearer-token-here
+
+node linkedin-agent.mjs "The future of AI agents in enterprise software"
+
+
Expected output:
+
+
🤖 Agent starting — topic: "The future of AI agents in enterprise software"
+
+🔧 Calling linkedin_create_post...
+ ✓ {"id":"urn:li:share:7194...","success":true}...
+
+✅ Agent finished:
+ I've published the following LinkedIn post:
+
+"Enterprise software is undergoing a quiet revolution..."
+[full post text]
+
+The post has been published successfully to your LinkedIn feed.
+
+
Step 4 — Extend it
+
+
Now that you have the agentic loop working, you can extend it:
+
+
Post to multiple platforms at once
+
// Replace the user message with:
+content: `Draft a post about "${ topic} " and publish it on both
+LinkedIn (professional tone) and Twitter (punchy, max 280 chars, 2 hashtags).
+Use linkedin_create_post and twitter_create_tweet.`
+
+
Schedule with a cron job
+
# Post every weekday at 9am
+0 9 * * 1-5 SQUAREMCP_TOKEN=... ANTHROPIC_API_KEY=... \
+ node /path/to/linkedin-agent.mjs "$(cat /path/to/topics.txt | shuf -n1)"
+
+
React to inbound WhatsApp messages
+
Configure a webhook in the SquareMCP dashboard. When a WhatsApp message arrives, SquareMCP POSTs to your server. Run the same agent loop but start with the inbound message as context:
+
+
import express from 'express' ;
+import crypto from 'crypto' ;
+
+const app = express ();
+app.use (express.json ());
+
+app.post ('/webhook' , async (req, res) => {
+ // Verify signature
+ const sig = req.headers['x-squaremcp-signature' ];
+ const expected = `sha256=${ crypto
+ .createHmac ('sha256' , process.env.WEBHOOK_SECRET )
+ .update (JSON.stringify (req.body))
+ .digest ('hex' )} ` ;
+ if (sig !== expected) { res.status (401 ).end (); return ; }
+
+ res.status (200 ).end (); // acknowledge immediately
+
+ const { platform, data } = req.body;
+ console.log (`Inbound from ${ platform} : ${ data.text} ` );
+
+ // Run agent in background...
+ runAgent (data).catch(console.error);
+});
+
+app.listen (3000 );
+
+
Going further
+
+
+
+
+
+
diff --git a/docs/getting-started.html b/docs/getting-started.html
new file mode 100644
index 0000000..54d14c9
--- /dev/null
+++ b/docs/getting-started.html
@@ -0,0 +1,185 @@
+
+
+
+
+
+Getting Started — SquareMCP Docs
+
+
+
+
+
+
+
+
+
+
+
Getting started
+
Connect your AI assistant to SquareMCP in five minutes. Choose your client below.
+
+
+
Step 1 — Create your account
+
+
+
+
Sign up at squaremcp.com
+ Open the
SquareMCP dashboard , create an account, and verify your email.
+
+
+
+
+ Get your access token
+ Click Connect MCP Client in the dashboard. This opens a short OAuth flow that issues a Bearer token bound to your account.
+ Copy the token shown on the confirmation page — it won't be displayed again.
+
+
+
+
+
Connect at least one platform
+ Go to
Platforms and connect LinkedIn, TikTok, WhatsApp, or any other service. See
Platform guides for step-by-step instructions per platform.
+
+
+
+
+
Step 2 — Configure your MCP client
+
+
+ Claude Desktop
+ Codex CLI
+ opencode
+
+
+
+
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
+// %APPDATA%\Claude\claude_desktop_config.json (Windows)
+{
+ "mcpServers" : {
+ "squaremcp" : {
+ "type" : "http" ,
+ "url" : "https://hermes.squaremcp.com/mcp" ,
+ "headers" : {
+ "Authorization" : "Bearer YOUR_TOKEN_HERE"
+ }
+ }
+ }
+}
+
Restart Claude Desktop after saving. You should see SquareMCP tools in the tool picker (hammer icon).
+
+
+
+
# ~/.codex/config.json
+{
+ "mcpServers" : {
+ "squaremcp" : {
+ "type" : "http" ,
+ "url" : "https://hermes.squaremcp.com/mcp" ,
+ "headers" : {
+ "Authorization" : "Bearer YOUR_TOKEN_HERE"
+ }
+ }
+ }
+}
+
Or pass inline per command:
+
codex --mcp-server squaremcp=https://hermes.squaremcp.com/mcp \
+ --mcp-header squaremcp:Authorization="Bearer YOUR_TOKEN_HERE" \
+ "Post a LinkedIn update about today's product launch"
+
+ PKCE flow (optional)
+ Codex CLI supports the full OAuth PKCE flow. Run codex auth squaremcp and follow the browser prompt — no token copy-paste required.
+
+
+
+
+
# ~/.config/opencode/config.json
+{
+ "mcp" : {
+ "servers" : {
+ "squaremcp" : {
+ "type" : "http" ,
+ "url" : "https://hermes.squaremcp.com/mcp" ,
+ "headers" : {
+ "Authorization" : "Bearer YOUR_TOKEN_HERE"
+ }
+ }
+ }
+ }
+}
+
Save the file and restart opencode. The SquareMCP tools will appear in the tool list automatically.
+
+
+
Step 3 — Verify the connection
+
Ask your AI assistant:
+
What social platforms do I have connected?
+
It should call get_profile or linkedin_get_profile and return your account details. If you see a "Platform not connected" error, revisit the Platform guides .
+
+
Available tools
+
SquareMCP exposes tools across every connected platform. A few highlights:
+
+
+
+
linkedin_create_post
+
Publish text, image, or video to your LinkedIn feed.
+
+
+
tiktok_create_video
+
Upload a video file and publish it to TikTok.
+
+
+
whatsapp_send_message
+
Send a WhatsApp message to any number via Business API.
+
+
+
twitter_create_tweet
+
Post a tweet with optional media attachment.
+
+
+
instagram_create_reel
+
Publish a reel to your Instagram Business account.
+
+
+
send_email
+
Send email from any connected IMAP/SMTP account.
+
+
+
+
See the full list in the API reference .
+
+
Troubleshooting
+
+
Tools not appearing in Claude
+
Restart Claude Desktop after editing claude_desktop_config.json. If tools still don't appear, open the Claude Desktop developer console and look for MCP connection errors.
+
+
"Platform not connected" errors
+
The tool was called but the platform isn't linked to your account. Open the dashboard and connect the platform under Platforms .
+
+
"Token expired" badge in dashboard
+
OAuth tokens for LinkedIn, TikTok, and Instagram expire. SquareMCP attempts an automatic refresh — if that fails, reconnect the platform. WhatsApp, Telegram, and Discord use long-lived bot tokens that don't expire.
+
+
Rate limit errors
+
Each SquareMCP plan has a monthly tool-call limit. Check Usage in the dashboard. Upgrade your plan if you're consistently hitting the limit.
+
+
+ Keep your Bearer token secret
+ Your Bearer token has full access to every connected platform. Treat it like a password. Rotate it from the dashboard if you suspect it's been exposed.
+
+
+
+
+
+
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..e6c29c8
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,94 @@
+
+
+
+
+
+SquareMCP Docs — AI Social Media Gateway
+
+
+
+
+
+
+
+
+
+
+
+
Build AI agents that talk to the world
+
SquareMCP connects Claude, Codex CLI, and opencode to LinkedIn, TikTok, WhatsApp, Instagram, Twitter, and more — through a single MCP server.
+
+
+
+
+
Why MCP?
+
The Model Context Protocol lets AI assistants call real tools without custom integrations. Instead of writing a bespoke connector for every model and platform, you configure SquareMCP once and every MCP-compatible client gains the same 50+ tools.
+
SquareMCP is multi-tenant by design: each customer's credentials are encrypted at rest, isolated per account, and never shared across sessions. Bearer tokens issued through OAuth are bound to your customer record so every tool call is attributable and rate-limited.
+
+
Supported clients
+
+
+
Claude Desktop
+
Add a mcpServers entry to claude_desktop_config.json. No extra software needed.
+
+
+
Codex CLI
+
Pass --mcp-server or configure in ~/.codex/config.json. Full PKCE OAuth flow supported.
+
+
+
opencode
+
Add SquareMCP to your opencode MCP providers list. HTTP Bearer transport.
+
+
+
Custom agents
+
Any MCP client that supports Streamable HTTP transport works. See the agent tutorial for a from-scratch example.
+
+
+
+
Supported platforms
+
+
in LinkedInPost text, images, and video. Search connections, send messages.
+
🎵 TikTokUpload and publish videos. View creator analytics.
+
💬 WhatsAppSend messages and templates. Receive inbound via webhook.
+
📷 InstagramPublish reels and image posts via Business API.
+
𝕏 Twitter / XTweet with media. Search and read timeline.
+
f FacebookPost to pages, share photos and video.
+
✈️ TelegramSend messages and photos via bot token.
+
🎮 DiscordSend messages to channels via bot.
+
+
+
+
+
+
diff --git a/docs/platforms.html b/docs/platforms.html
new file mode 100644
index 0000000..2624cd9
--- /dev/null
+++ b/docs/platforms.html
@@ -0,0 +1,288 @@
+
+
+
+
+
+Platform Guides — SquareMCP Docs
+
+
+
+
+
+
+
+
+
+
+
Platform guides
+
Step-by-step instructions for connecting each platform. Click a platform to jump to its guide.
+
+
+
+
+
+
in LinkedIn
+
+
What you can do
+
+ Post text updates, articles, images, and videos
+ Search connections and send direct messages
+ Retrieve your profile and network info
+
+
+
Connecting LinkedIn
+
+
+
+
Create a LinkedIn app
+ Go to
developer.linkedin.com/apps and create a new app. Add your company page and request the
w_member_social and
r_liteprofile products.
+
+
+
+
+ Generate an access token
+ Use the LinkedIn OAuth 2.0 token generator in your app dashboard, or use the 3-legged OAuth flow. Copy the access token (valid 60 days; refresh tokens extend to ~12 months).
+
+
+
+
+ Paste into SquareMCP dashboard
+ Open the SquareMCP dashboard → LinkedIn → Connect → paste your access token → Save.
+
+
+
+
+
Available tools
+
Example prompts
+
Post a LinkedIn update: "Excited to announce our new product launch!"
+
+Post a LinkedIn video from /tmp/demo.mp4 with caption "Watch our product demo"
+
+Search my LinkedIn connections for people at Anthropic
+
+Send a LinkedIn message to John Smith saying "Great meeting you at the conference"
+
+
+ Token refresh
+ LinkedIn access tokens expire after 60 days. SquareMCP will automatically attempt a refresh using the refresh token. If refresh fails (e.g., the user revokes access), reconnect from the dashboard.
+
+
+
+
🎵 TikTok
+
+
What you can do
+
+ Upload and publish videos with captions
+ Set privacy level (public, friends, private)
+ View creator info and video status
+
+
+
Connecting TikTok
+
+
+
+ OAuth via SquareMCP dashboard
+ Open the SquareMCP dashboard → TikTok → Connect. You'll be redirected to TikTok to authorise the app. SquareMCP uses TikTok Login Kit with video.publish and user.info.basic scopes.
+
+
+
+
+ Allow the requested permissions
+ TikTok shows the requested scopes. Click Authorize . You'll be redirected back to the dashboard with a "Connected" badge.
+
+
+
+
+
Uploading a video
+
Videos must be hosted at a publicly accessible URL. The tool uploads the video to TikTok's servers asynchronously — use tiktok_get_video_status to poll for completion.
+
+
Example prompts
+
Upload the video at https://cdn.example.com/demo.mp4 to TikTok
+with caption "Check out our new feature! #AI #productlaunch"
+and set privacy to PUBLIC_TO_EVERYONE
+
+Check the status of my last TikTok upload
+
+
+ TikTok sandbox vs. production
+ TikTok's Content Posting API requires app approval for production use. New apps start in sandbox mode (videos published to a test account only). Apply for production access through the TikTok developer portal.
+
+
+
+
💬 WhatsApp
+
+
What you can do
+
+ Send freeform messages and approved template messages
+ Send images, documents, and media
+ Receive inbound messages via webhook and forward to your AI agent
+ List all approved message templates
+
+
+
Prerequisites
+
You need a WhatsApp Business Account and a phone number registered through the Meta Business Manager .
+
+
Connecting WhatsApp
+
+
+
+
Get your credentials from Meta
+ In Meta for Developers → Your App → WhatsApp → API Setup:
+
+ Phone Number ID — the ID of your registered number
+ Business Account ID — your WABA ID
+ Permanent Access Token — generate from System Users in Business Manager
+
+
+
+
+
+ Enter credentials in dashboard
+ Open SquareMCP dashboard → WhatsApp → Connect → paste all three values → Save.
+
+
+
+
+ Configure the webhook (optional — for inbound messages)
+ In Meta for Developers → Webhooks, set the callback URL to https://hermes.squaremcp.com/webhook/whatsapp and the verify token to your WA_VERIFY_TOKEN (ask support for this). Subscribe to the messages field.
+ Then configure a forwarding URL in SquareMCP dashboard → Webhooks so inbound messages reach your server.
+
+
+
+
+
Sending messages
+
Freeform message (within 24-hour session window)
+
Send a WhatsApp message to +447911123456 saying:
+"Hi! Your order #12345 has shipped and will arrive tomorrow."
+
+
Template message (anytime)
+
List my WhatsApp message templates
+
+Send the "order_confirmation" template to +447911123456
+with variables: order_id=12345, delivery_date="14 May 2026"
+
+
+ 24-hour rule
+ WhatsApp only allows freeform messages within 24 hours of a customer contacting you first. Outside that window, use approved template messages.
+
+
+
+
📷 Instagram
+
+
What you can do
+
+ Publish image posts and reels
+ View profile and recent media
+
+
+
Connecting Instagram
+
Instagram posting requires a Business or Creator account connected to a Facebook Page.
+
+
+
+
+
Get a Graph API access token
+ Open the
Graph API Explorer , select your app, and request
instagram_basic,
instagram_content_publish, and
pages_read_engagement permissions. Generate a User Access Token and exchange it for a long-lived token.
+
+
+
+
+ Find your Business Account ID
+ Call GET /me/accounts then GET /{page_id}?fields=instagram_business_account to find your Instagram Business Account ID.
+
+
+
+
+ Enter credentials in dashboard
+ SquareMCP dashboard → Instagram → Connect → paste the access token and Business Account ID.
+
+
+
+
+
Example prompts
+
Post an Instagram reel from https://cdn.example.com/reel.mp4
+with caption "Behind the scenes of our product shoot 🎬 #startup"
+
+Get my last 10 Instagram posts
+
+
+
+
+
Connecting Twitter / X
+
+
+
+
Create a developer app
+ Go to
developer.twitter.com and create a project and app. Enable
OAuth 1.0a with Read and Write permissions.
+
+
+
+
+ Generate all four credentials
+ In your app → Keys and tokens: copy the API Key , API Secret , Access Token , and Access Token Secret (all four are required).
+
+
+
+
+ Enter credentials in dashboard
+ SquareMCP dashboard → Twitter / X → Connect → paste all four values.
+
+
+
+
+
Example prompts
+
Tweet: "We just shipped our v2 release 🚀 Read the changelog at squaremcp.com"
+
+Upload the video at /tmp/demo.mp4 and tweet "Watch our new feature demo!"
+
+Search Twitter for tweets mentioning "SquareMCP"
+
+
+
✈️ Telegram
+
+
Connecting Telegram
+
+
+
+ Create a bot with BotFather
+ Open Telegram and message @BotFather. Send /newbot, choose a name and username, and copy the API token it provides (format: 123456789:ABCdef...).
+
+
+
+
+ Enter the token in dashboard
+ SquareMCP dashboard → Telegram → Connect → paste the bot token.
+
+
+
+
+ Add your bot to a chat or group
+ Bots can only send messages to chats they're a member of. Add your bot to the target chat, then use telegram_get_updates to find the chat ID.
+
+
+
+
+
Example prompts
+
Send a Telegram message to chat ID -1001234567890 saying "Daily report ready!"
+
+Get the latest Telegram messages in my channel
+
+
+
+
diff --git a/docs/styles.css b/docs/styles.css
new file mode 100644
index 0000000..35aeb18
--- /dev/null
+++ b/docs/styles.css
@@ -0,0 +1,293 @@
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+:root {
+ --bg: #0f0f10;
+ --surface: #1a1a1b;
+ --surface-hover: #222223;
+ --border: #2a2a2b;
+ --text: #e5e5e5;
+ --text-secondary: #888;
+ --accent: #10a37f;
+ --accent-hover: #0d8c6d;
+ --code-bg: #161617;
+ --radius: 10px;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ line-height: 1.6;
+}
+
+a { color: var(--accent); text-decoration: none; }
+a:hover { text-decoration: underline; }
+
+/* ── Nav ── */
+.site-nav {
+ background: var(--surface);
+ border-bottom: 1px solid var(--border);
+ position: sticky;
+ top: 0;
+ z-index: 10;
+}
+
+.nav-inner {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 0 24px;
+ height: 56px;
+ display: flex;
+ align-items: center;
+ gap: 32px;
+}
+
+.nav-logo {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-weight: 700;
+ font-size: 16px;
+ color: var(--text);
+ text-decoration: none;
+}
+
+.nav-logo-mark {
+ width: 28px;
+ height: 28px;
+ background: linear-gradient(135deg, var(--accent), #0d8c6d);
+ border-radius: 7px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-weight: 700;
+ font-size: 14px;
+}
+
+.nav-links {
+ display: flex;
+ gap: 4px;
+ flex: 1;
+}
+
+.nav-links a {
+ padding: 6px 12px;
+ border-radius: 6px;
+ font-size: 14px;
+ color: var(--text-secondary);
+ transition: all 0.15s;
+}
+
+.nav-links a:hover { color: var(--text); background: var(--surface-hover); text-decoration: none; }
+.nav-links a.active { color: var(--text); background: var(--bg); }
+
+.nav-cta {
+ background: var(--accent);
+ color: #fff !important;
+ padding: 7px 16px;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 600;
+ transition: background 0.15s;
+}
+.nav-cta:hover { background: var(--accent-hover) !important; text-decoration: none !important; }
+
+/* ── Layout ── */
+.page {
+ max-width: 860px;
+ margin: 0 auto;
+ padding: 56px 24px 96px;
+}
+
+.hero {
+ margin-bottom: 48px;
+}
+
+.hero h1 {
+ font-size: 36px;
+ font-weight: 700;
+ line-height: 1.25;
+ margin-bottom: 12px;
+}
+
+.hero p {
+ font-size: 18px;
+ color: var(--text-secondary);
+ max-width: 620px;
+}
+
+/* ── Section headers ── */
+h2 {
+ font-size: 22px;
+ font-weight: 600;
+ margin: 48px 0 16px;
+ padding-top: 8px;
+ border-top: 1px solid var(--border);
+}
+
+h3 {
+ font-size: 17px;
+ font-weight: 600;
+ margin: 28px 0 10px;
+ color: var(--text);
+}
+
+p { margin-bottom: 14px; color: var(--text); }
+
+ul, ol { padding-left: 20px; margin-bottom: 14px; }
+li { margin-bottom: 6px; }
+
+/* ── Code ── */
+pre {
+ background: var(--code-bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 20px 24px;
+ overflow-x: auto;
+ margin: 16px 0;
+ position: relative;
+}
+
+code {
+ font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
+ font-size: 13px;
+ line-height: 1.7;
+ color: #d4d4d4;
+}
+
+p code, li code {
+ background: #2a2a2b;
+ padding: 2px 7px;
+ border-radius: 5px;
+ font-size: 12.5px;
+ color: #e5e5e5;
+}
+
+.code-label {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--text-secondary);
+ margin-bottom: 6px;
+ margin-top: 20px;
+}
+
+/* Syntax colours */
+.kw { color: #569cd6; }
+.str { color: #ce9178; }
+.cmt { color: #6a9955; }
+.fn { color: #dcdcaa; }
+.num { color: #b5cea8; }
+.cls { color: #4ec9b0; }
+
+/* ── Callout ── */
+.callout {
+ background: rgba(16, 163, 127, 0.08);
+ border: 1px solid rgba(16, 163, 127, 0.25);
+ border-radius: var(--radius);
+ padding: 16px 20px;
+ margin: 20px 0;
+ font-size: 14px;
+}
+
+.callout-warn {
+ background: rgba(245, 158, 11, 0.08);
+ border-color: rgba(245, 158, 11, 0.25);
+}
+
+.callout strong { display: block; margin-bottom: 4px; }
+
+/* ── Steps ── */
+.steps { counter-reset: step; margin: 0; padding: 0; list-style: none; }
+
+.steps li {
+ counter-increment: step;
+ display: flex;
+ gap: 16px;
+ margin-bottom: 28px;
+}
+
+.steps li::before {
+ content: counter(step);
+ background: var(--accent);
+ color: #fff;
+ width: 26px;
+ height: 26px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ font-weight: 700;
+ flex-shrink: 0;
+ margin-top: 2px;
+}
+
+.steps li > div { flex: 1; }
+.steps li strong { display: block; margin-bottom: 4px; font-size: 15px; }
+
+/* ── Cards ── */
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: 14px;
+ margin: 20px 0;
+}
+
+.card {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 20px;
+ transition: border-color 0.15s;
+}
+
+.card:hover { border-color: #3a3a3b; }
+.card h4 { font-size: 15px; margin-bottom: 6px; }
+.card p { font-size: 13px; color: var(--text-secondary); margin: 0; }
+
+/* ── Tab switcher ── */
+.tabs { display: flex; gap: 4px; margin-bottom: -1px; }
+.tab {
+ padding: 8px 16px;
+ background: transparent;
+ border: 1px solid transparent;
+ border-bottom: none;
+ border-radius: 8px 8px 0 0;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: all 0.15s;
+}
+.tab.active { background: var(--code-bg); border-color: var(--border); color: var(--text); }
+.tab-content { display: none; }
+.tab-content.active { display: block; }
+.tab-panel { border: 1px solid var(--border); border-radius: 0 var(--radius) var(--radius) var(--radius); }
+.tab-panel pre { border: none; border-radius: 0 var(--radius) var(--radius) var(--radius); margin: 0; }
+
+/* ── Badge ── */
+.badge {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 10px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.03em;
+}
+.badge-green { background: rgba(16,163,127,0.15); color: var(--accent); }
+.badge-blue { background: rgba(59,130,246,0.15); color: #60a5fa; }
+.badge-amber { background: rgba(245,158,11,0.15); color: #f59e0b; }
+
+/* ── Platform icon ── */
+.platform-icon { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; }
+.icon { width: 24px; height: 24px; border-radius: 6px; display: inline-flex; align-items: center; justify-content: center; font-size: 13px; }
+
+@media (max-width: 640px) {
+ .hero h1 { font-size: 26px; }
+ .nav-links { display: none; }
+ pre { padding: 14px 16px; }
+}
diff --git a/package-lock.json b/package-lock.json
index c9a69ff..70a9f0d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,11 +28,107 @@
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20.0.0",
"@types/nodemailer": "^6.4.0",
+ "@vitest/coverage-v8": "^4.1.6",
"pixelmatch": "^7.1.0",
"playwright": "^1.59.1",
"pngjs": "^7.0.0",
"tsx": "^4.0.0",
- "typescript": "^5.0.0"
+ "typescript": "^5.0.0",
+ "vitest": "^4.1.6"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
+ "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
@@ -489,6 +585,34 @@
"hono": "^4"
}
},
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.27.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
@@ -814,6 +938,35 @@
"node": ">= 0.6"
}
},
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
+ "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.129.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
+ "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
"node_modules/@pinojs/redact": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
@@ -892,6 +1045,288 @@
"@redis/client": "^5.12.1"
}
},
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
+ "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz",
+ "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
+ "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
+ "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz",
+ "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz",
+ "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
+ "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz",
+ "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz",
+ "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz",
+ "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz",
+ "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
+ "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz",
+ "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz",
+ "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz",
+ "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
+ "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
@@ -910,6 +1345,17 @@
"@types/node": "*"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@@ -939,6 +1385,20 @@
"@types/node": "*"
}
},
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/express": {
"version": "4.17.25",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
@@ -1063,6 +1523,150 @@
"@types/node": "*"
}
},
+ "node_modules/@vitest/coverage-v8": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz",
+ "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^1.0.2",
+ "@vitest/utils": "4.1.6",
+ "ast-v8-to-istanbul": "^1.0.0",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.2.0",
+ "magicast": "^0.5.2",
+ "obug": "^2.1.1",
+ "std-env": "^4.0.0-rc.1",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@vitest/browser": "4.1.6",
+ "vitest": "4.1.6"
+ },
+ "peerDependenciesMeta": {
+ "@vitest/browser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
+ "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz",
+ "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.6",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz",
+ "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz",
+ "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.6",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz",
+ "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
+ "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz",
+ "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/@zone-eu/mailsplit": {
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz",
@@ -1126,6 +1730,28 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ast-v8-to-istanbul": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
+ "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "estree-walker": "^3.0.3",
+ "js-tokens": "^10.0.0"
+ }
+ },
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
@@ -1236,6 +1862,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@@ -1266,6 +1902,13 @@
"node": ">= 0.6"
}
},
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
@@ -1368,6 +2011,16 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@ -1445,6 +2098,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -1505,6 +2165,16 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -1535,6 +2205,16 @@
"node": ">=18.0.0"
}
},
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
@@ -1621,6 +2301,24 @@
],
"license": "BSD-3-Clause"
},
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
@@ -1752,6 +2450,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -1785,6 +2493,13 @@
"node": ">=16.9.0"
}
},
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -1901,6 +2616,45 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/jose": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.0.tgz",
@@ -1910,6 +2664,13 @@
"url": "https://github.com/sponsors/panva"
}
},
+ "node_modules/js-tokens": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
+ "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -2007,6 +2768,267 @@
"integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==",
"license": "MIT"
},
+ "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": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.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"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -2070,6 +3092,44 @@
"url": "https://github.com/sponsors/wellwelwel"
}
},
+ "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/magicast": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
+ "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -2195,6 +3255,25 @@
"node": ">=8.0.0"
}
},
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -2234,6 +3313,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
"node_modules/on-exit-leak-free": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
@@ -2288,6 +3378,33 @@
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/pino": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
@@ -2404,6 +3521,35 @@
"node": ">=14.19.0"
}
},
+ "node_modules/postcss": {
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
"node_modules/process-warning": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
@@ -2538,6 +3684,40 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
+ "node_modules/rolldown": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
+ "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.129.0",
+ "@rolldown/pluginutils": "1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0",
+ "@rolldown/binding-darwin-arm64": "1.0.0",
+ "@rolldown/binding-darwin-x64": "1.0.0",
+ "@rolldown/binding-freebsd-x64": "1.0.0",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0",
+ "@rolldown/binding-linux-x64-musl": "1.0.0",
+ "@rolldown/binding-openharmony-arm64": "1.0.0",
+ "@rolldown/binding-wasm32-wasi": "1.0.0",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0"
+ }
+ },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -2778,6 +3958,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
@@ -2811,6 +3998,16 @@
"atomic-sleep": "^1.0.0"
}
},
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -2835,6 +4032,13 @@
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
}
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -2844,6 +4048,26 @@
"node": ">= 0.8"
}
},
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/thread-stream": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
@@ -2856,6 +4080,50 @@
"node": ">=20"
}
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
+ "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -2865,6 +4133,14 @@
"node": ">=0.6"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
"node_modules/tsx": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
@@ -2945,6 +4221,174 @@
"node": ">= 0.8"
}
},
+ "node_modules/vite": {
+ "version": "8.0.12",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz",
+ "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.14",
+ "rolldown": "1.0.0",
+ "tinyglobby": "^0.2.16"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.18",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz",
+ "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.6",
+ "@vitest/mocker": "4.1.6",
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/runner": "4.1.6",
+ "@vitest/snapshot": "4.1.6",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.6",
+ "@vitest/browser-preview": "4.1.6",
+ "@vitest/browser-webdriverio": "4.1.6",
+ "@vitest/coverage-istanbul": "4.1.6",
+ "@vitest/coverage-v8": "4.1.6",
+ "@vitest/ui": "4.1.6",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -2960,6 +4404,23 @@
"node": ">= 8"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/package.json b/package.json
index 238f9aa..7b4c324 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,9 @@
"test:product-site:e2e": "node product/site/e2e-test.mjs",
"test:product-site:verify": "node product/site/verify.mjs",
"test:product-site:cleanup": "node product/site/cleanup-test-submissions.mjs",
- "deploy:product-site:verify": "bash product/site/deploy-and-verify.sh"
+ "deploy:product-site:verify": "bash product/site/deploy-and-verify.sh",
+ "test": "vitest run",
+ "test:watch": "vitest"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
@@ -36,10 +38,12 @@
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20.0.0",
"@types/nodemailer": "^6.4.0",
+ "@vitest/coverage-v8": "^4.1.6",
"pixelmatch": "^7.1.0",
"playwright": "^1.59.1",
"pngjs": "^7.0.0",
"tsx": "^4.0.0",
- "typescript": "^5.0.0"
+ "typescript": "^5.0.0",
+ "vitest": "^4.1.6"
}
}
diff --git a/product/app/app.js b/product/app/app.js
index 43fb3d2..9870da2 100644
--- a/product/app/app.js
+++ b/product/app/app.js
@@ -24,10 +24,14 @@ const modalClose = document.querySelector('.modal-close');
const modalBackdrop = document.querySelector('.modal-backdrop');
const navLinks = document.querySelectorAll('.nav-link');
const platformGrid = document.querySelector('.platform-grid');
+const analyticsSection = document.getElementById('analytics-section');
const invoicesSection = document.getElementById('invoices-section');
const adminSection = document.getElementById('admin-section');
const adminNav = document.getElementById('admin-nav');
+let platformChartInstance = null;
+let dailyChartInstance = null;
+
// Platform config
const PLATFORM_CONFIG = {
tiktok: {
@@ -178,8 +182,14 @@ navLinks.forEach(link => {
platformGrid.classList.toggle('hidden', view !== 'platforms');
document.querySelector('.welcome').classList.toggle('hidden', view !== 'platforms');
document.querySelector('.usage-bar').classList.toggle('hidden', view !== 'platforms');
+ analyticsSection.classList.toggle('hidden', view !== 'analytics');
invoicesSection.classList.toggle('hidden', view !== 'invoices');
adminSection.classList.toggle('hidden', view !== 'admin');
+ document.getElementById('webhooks-section').classList.toggle('hidden', view !== 'webhooks');
+ if (view === 'analytics') loadAnalytics();
+ if (view === 'invoices') loadInvoices();
+ if (view === 'admin') loadAdminPanel();
+ if (view === 'webhooks') loadWebhookConfig();
});
});
@@ -227,6 +237,11 @@ logoutBtn.addEventListener('click', async () => {
showLogin();
});
+// Connect MCP Client — start the browser OAuth flow
+document.getElementById('connect-mcp-btn')?.addEventListener('click', () => {
+ window.open(`${API_BASE}/oauth/connect-mcp`, '_blank', 'width=560,height=600,noopener');
+});
+
// Password reset request
resetRequestForm?.addEventListener('submit', async (e) => {
e.preventDefault();
@@ -291,21 +306,35 @@ loginForm.appendChild(forgotLink);
// Connection status
async function updateConnectionStatus() {
try {
- const data = await apiGet('/api/connections');
- const connections = data.connections || {};
+ const [connData, healthData] = await Promise.all([
+ apiGet('/api/connections').catch(() => ({ connections: {} })),
+ apiGet('/api/health/platforms').catch(() => ({ health: [] })),
+ ]);
+ const connections = connData.connections || {};
+ const healthMap = {};
+ (healthData.health || []).forEach(h => { healthMap[h.platform] = h.status; });
+
document.querySelectorAll('.platform-card').forEach(card => {
const platform = card.dataset.platform;
const badge = card.querySelector('.status-badge');
const btn = card.querySelector('.btn-connect');
- if (connections[platform]) {
+ const health = healthMap[platform];
+
+ if (health === 'healthy') {
badge.textContent = 'Connected';
- badge.classList.remove('disconnected');
- badge.classList.add('connected');
+ badge.className = 'status-badge connected';
+ btn.textContent = 'Manage';
+ } else if (health === 'expired') {
+ badge.textContent = 'Token expired';
+ badge.className = 'status-badge expired';
+ btn.textContent = 'Reconnect';
+ } else if (connections[platform]) {
+ badge.textContent = 'Connected';
+ badge.className = 'status-badge connected';
btn.textContent = 'Manage';
} else {
badge.textContent = 'Not connected';
- badge.classList.remove('connected');
- badge.classList.add('disconnected');
+ badge.className = 'status-badge disconnected';
btn.textContent = 'Connect';
}
});
@@ -364,6 +393,93 @@ async function loadInvoices() {
}
}
+// Analytics
+const PLATFORM_COLORS = {
+ email: '#ea4335', linkedin: '#0a66c2', twitter: '#000000',
+ instagram: '#e1306c', facebook: '#1877f2', tiktok: '#010101',
+ whatsapp: '#25d366', telegram: '#0088cc', discord: '#5865f2',
+ snapchat: '#fffc00', obsidian: '#7c3aed',
+};
+
+async function loadAnalytics() {
+ const emptyEl = document.getElementById('analytics-empty');
+ try {
+ const [usageData, dailyData] = await Promise.all([
+ apiGet('/api/usage'),
+ apiGet('/api/usage/daily'),
+ ]);
+
+ const breakdown = usageData.breakdown || {};
+ const daily = dailyData.daily || [];
+
+ const hasData = Object.keys(breakdown).length > 0 || daily.length > 0;
+ emptyEl.classList.toggle('hidden', hasData);
+ document.querySelector('.charts-grid').classList.toggle('hidden', !hasData);
+
+ if (!hasData) return;
+
+ // Platform breakdown chart
+ const platformLabels = Object.keys(breakdown);
+ const platformCounts = platformLabels.map(p => breakdown[p]);
+ const platformColors = platformLabels.map(p => PLATFORM_COLORS[p] || '#888');
+
+ if (platformChartInstance) platformChartInstance.destroy();
+ platformChartInstance = new Chart(document.getElementById('platform-chart'), {
+ type: 'bar',
+ data: {
+ labels: platformLabels,
+ datasets: [{
+ label: 'Calls',
+ data: platformCounts,
+ backgroundColor: platformColors,
+ borderRadius: 6,
+ }],
+ },
+ options: {
+ indexAxis: 'y',
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: { legend: { display: false } },
+ scales: {
+ x: { ticks: { color: '#888' }, grid: { color: '#2a2a2b' } },
+ y: { ticks: { color: '#e5e5e5' }, grid: { display: false } },
+ },
+ },
+ });
+
+ // Daily activity chart
+ const dailyLabels = daily.map(d => d.date);
+ const dailyCounts = daily.map(d => Number(d.count));
+
+ if (dailyChartInstance) dailyChartInstance.destroy();
+ dailyChartInstance = new Chart(document.getElementById('daily-chart'), {
+ type: 'bar',
+ data: {
+ labels: dailyLabels,
+ datasets: [{
+ label: 'Calls',
+ data: dailyCounts,
+ backgroundColor: '#10a37f',
+ borderRadius: 4,
+ }],
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: { legend: { display: false } },
+ scales: {
+ x: { ticks: { color: '#888', maxRotation: 45 }, grid: { color: '#2a2a2b' } },
+ y: { ticks: { color: '#888' }, grid: { color: '#2a2a2b' }, beginAtZero: true },
+ },
+ },
+ });
+ } catch {
+ emptyEl.classList.remove('hidden');
+ emptyEl.querySelector('p').textContent = 'Failed to load analytics.';
+ document.querySelector('.charts-grid').classList.add('hidden');
+ }
+}
+
// Admin panel
async function loadAdminPanel() {
try {
@@ -528,5 +644,51 @@ async function checkSession() {
}
}
+// Webhook config
+async function loadWebhookConfig() {
+ try {
+ const data = await apiGet('/api/webhooks/config');
+ const display = document.getElementById('webhook-url-display');
+ const deleteBtn = document.getElementById('webhook-delete-btn');
+ const input = document.getElementById('webhook-url-input');
+ if (data.webhookUrl) {
+ display.textContent = data.webhookUrl;
+ deleteBtn.classList.remove('hidden');
+ input.value = data.webhookUrl;
+ } else {
+ display.textContent = 'No webhook configured';
+ deleteBtn.classList.add('hidden');
+ input.value = '';
+ }
+ document.getElementById('webhook-secret-box').classList.add('hidden');
+ } catch {
+ // ignore
+ }
+}
+
+document.getElementById('webhook-form')?.addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const url = document.getElementById('webhook-url-input').value.trim();
+ const data = await apiPost('/api/webhooks/config', { webhook_url: url });
+ if (data.error) { alert(data.error); return; }
+ document.getElementById('webhook-url-display').textContent = data.webhookUrl;
+ document.getElementById('webhook-delete-btn').classList.remove('hidden');
+ if (data.webhookSecret) {
+ document.getElementById('webhook-secret-value').textContent = data.webhookSecret;
+ document.getElementById('webhook-secret-box').classList.remove('hidden');
+ }
+});
+
+document.getElementById('webhook-delete-btn')?.addEventListener('click', async () => {
+ if (!confirm('Remove webhook? This cannot be undone.')) return;
+ await fetch(`${API_BASE}/api/webhooks/config`, { method: 'DELETE', credentials: 'include' });
+ loadWebhookConfig();
+});
+
+window.copyWebhookSecret = () => {
+ const val = document.getElementById('webhook-secret-value').textContent;
+ navigator.clipboard.writeText(val).then(() => alert('Secret copied!'));
+};
+
// Init
checkSession();
diff --git a/product/app/index.html b/product/app/index.html
index 232dfa3..83ff784 100644
--- a/product/app/index.html
+++ b/product/app/index.html
@@ -80,7 +80,9 @@
+
+ Analytics
+ Tool calls this month, by platform and day
+
+
+
No activity yet this month. Make your first tool call to see data here.
+
+
+
Invoices
@@ -212,6 +233,29 @@
Admin Panel
+
+
+ Webhook
+ Receive real-time events when messages arrive on your connected platforms.
+
+
+ No webhook configured
+ Remove
+
+
+
+
Signing secret — copy now, not shown again:
+
+
Copy secret
+
+
+
Verify each request by computing sha256=HMAC-SHA256(secret, rawBody) and comparing to the X-SquareMCP-Signature header.
+
+
+
@@ -225,6 +269,7 @@
+