diff --git a/.github/workflows/publish-rc.yml b/.github/workflows/publish-rc.yml index 98ba3783..ba80ab44 100644 --- a/.github/workflows/publish-rc.yml +++ b/.github/workflows/publish-rc.yml @@ -22,6 +22,16 @@ jobs: - uses: astral-sh/setup-uv@v7 + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: "24" + cache: "pnpm" + cache-dependency-path: ui/pnpm-lock.yaml + + - name: Enable pnpm + run: corepack enable + - name: Compute RC version run: | CURRENT=$(grep '^version = ' pyproject.toml | sed 's/version = "//;s/"//') @@ -29,6 +39,9 @@ jobs: RC_VERSION="${BASE}rc${{ github.run_number }}" sed -i "s/^version = \".*\"/version = \"${RC_VERSION}\"/" pyproject.toml + - name: Build static UI + run: ./scripts/build-ui.sh + - name: Build run: uv build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a63da995..646533e9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,6 +17,19 @@ jobs: - uses: astral-sh/setup-uv@v7 + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: "24" + cache: "pnpm" + cache-dependency-path: ui/pnpm-lock.yaml + + - name: Enable pnpm + run: corepack enable + + - name: Build static UI + run: ./scripts/build-ui.sh + - name: Build distributions run: uv build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6cc75c50..b9361202 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,29 +13,47 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - + + - uses: astral-sh/setup-uv@v7 + - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.13" cache: "pip" - + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: "24" + cache: "pnpm" + cache-dependency-path: ui/pnpm-lock.yaml + - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e ".[dev]" + run: uv pip install --system -e ".[dev]" + + - name: Build static UI + run: ./scripts/build-ui.sh + + - name: Lint static UI + run: uv run pnpm --dir ui lint - name: Lint with ruff run: | - ruff check src/ tests/ - ruff format --check src/ tests/ - + uv run ruff check src/ tests/ + uv run ruff format --check src/ tests/ + - name: Type check with ty - run: ty check src/ - + run: uv run ty check src/ + - name: Run tests with coverage - run: pytest --cov=authsome --cov-report=xml --cov-report=term -p no:xdist - + run: uv run pytest --cov=authsome --cov-report=xml --cov-report=term -p no:xdist + - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v6 with: diff --git a/.gitignore b/.gitignore index 441532c8..1fc1db07 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ downloads/ eggs/ .eggs/ lib/ +!ui/src/lib/ +!ui/src/lib/** lib64/ parts/ sdist/ @@ -204,6 +206,10 @@ cython_debug/ # Ruff stuff: .ruff_cache/ +# Generated static dashboard bundle +src/authsome/ui/web/* +!src/authsome/ui/web/.gitkeep + # PyPI configuration file .pypirc @@ -252,4 +258,7 @@ __marimo__/ docs/superpowers/ # Authsome skill (generated at eval time) -.claude/skills/authsome/ \ No newline at end of file +.claude/skills/authsome/ + +/tmp +/results diff --git a/docs/site/concepts/provider-registry.mdx b/docs/site/concepts/provider-registry.mdx index df4600d4..9a1972fd 100644 --- a/docs/site/concepts/provider-registry.mdx +++ b/docs/site/concepts/provider-registry.mdx @@ -42,7 +42,7 @@ Every provider definition declares: - An optional `api_url` for proxy routing. - An optional `export.env` map describing which environment variables hold which credential fields. -For full templates and the field-by-field schema, see [Provider schema](/reference/provider-schema). For a worked OAuth example using GitHub, see [GitHub integration](/integrations/oauth/github). For an API-key example using OpenAI, see [OpenAI integration](/integrations/api-key/openai). +For full templates and the field-by-field schema. For a worked OAuth example using GitHub, see [GitHub integration](/integrations/oauth/github). For an API-key example using OpenAI, see [OpenAI integration](/integrations/api-key/openai). ## Bundled providers do not imply bundled credentials diff --git a/docs/site/docs.json b/docs/site/docs.json index 544d6b17..4103c299 100644 --- a/docs/site/docs.json +++ b/docs/site/docs.json @@ -9,20 +9,28 @@ "favicon": "/favicon.svg", "description": "Local credential broker for AI agents. Log in once via OAuth2 or API key. Authsome keeps tokens fresh and injects them at runtime.", "colors": { - "primary": "#83CA16", - "light": "#A3E635", - "dark": "#65A30D" + "primary": "#10B981", + "light": "#4EDEA3", + "dark": "#059669" }, "background": { "color": { "light": "#FFFFFF", - "dark": "#0A0A0A" + "dark": "#09090B" } }, "appearance": { "default": "dark", "strict": false }, + "font": { + "headings": { + "family": "Hanken Grotesk" + }, + "body": { + "family": "Hanken Grotesk" + } + }, "banner": { "content": "Authsome is in active development. [Star us on GitHub](https://github.com/agentrhq/authsome) to follow along.", "dismissible": true diff --git a/docs/site/guides/custom-providers.mdx b/docs/site/guides/custom-providers.mdx index 7a402bbc..e310840f 100644 --- a/docs/site/guides/custom-providers.mdx +++ b/docs/site/guides/custom-providers.mdx @@ -139,7 +139,7 @@ Pick a template based on the auth type. | `device_code` | Headless OAuth2. User enters a code on a separate device. Needs `client_id`. | | `api_key` | Single secret with a header. Optional `env_var` for scripted setup. | -For the full schema with every supported field, see the [Provider schema reference](/reference/provider-schema). +Every supported field is listed in the JSON templates above. ## Step 4: Register the provider @@ -198,9 +198,6 @@ authsome list # github now shows source=custom ## What's next - - Every field in a provider definition. - How resolution and overrides work. diff --git a/docs/site/installation.mdx b/docs/site/installation.mdx index 6ca1e239..eaebaf15 100644 --- a/docs/site/installation.mdx +++ b/docs/site/installation.mdx @@ -113,7 +113,6 @@ export AUTHSOME_HOME=/var/lib/authsome authsome init ``` -See [Filesystem layout](/reference/file-layout) for the full directory model. ## Choose the encryption backend diff --git a/docs/site/integrations/agents/claude-code.mdx b/docs/site/integrations/agents/claude-code.mdx index 1b5f6b00..38cd18be 100644 --- a/docs/site/integrations/agents/claude-code.mdx +++ b/docs/site/integrations/agents/claude-code.mdx @@ -153,13 +153,12 @@ Claude runs: authsome get github --connection work --field access_token --show-secret ``` -For the underlying model, see [Profiles vs connections](/concepts/profiles-vs-connections). ## What the skill cannot do - **It cannot type a `client_secret` for you.** Authsome refuses sensitive values as command-line arguments. The browser bridge is the only path. - **It cannot bypass the proxy CA requirement.** HTTPS interception needs the mitmproxy CA trusted on the machine. The first `authsome run` writes the CA to `~/.mitmproxy/`. See [Proxy networking](/troubleshooting/proxy-networking). -- **It cannot share the vault across machines.** Claude Code on a second machine has its own `~/.authsome/`. Re-run logins there, or use a hosted daemon (see [Hosted deployment model](/security/hosted-deployment)). +- **It cannot share the vault across machines.** Claude Code on a second machine has its own `~/.authsome/`. Re-run logins there. ## Troubleshooting diff --git a/docs/site/integrations/agents/cowork.mdx b/docs/site/integrations/agents/cowork.mdx index 1fa92c8e..873d2050 100644 --- a/docs/site/integrations/agents/cowork.mdx +++ b/docs/site/integrations/agents/cowork.mdx @@ -29,7 +29,6 @@ auth.create_profile("pr-bot", description="Auto-PR agent") auth.create_profile("reviewer", description="Code review agent") ``` -See [Profiles](/guides/profiles) and [Profiles vs connections](/concepts/profiles-vs-connections). ## Run each agent under its own proxy @@ -56,13 +55,3 @@ Each agent's HTTP_PROXY points at a different daemon instance, each backed by a | Two agents accidentally share credentials | They're using the same `AUTHSOME_HOME`. Split them. | | Port 7998 conflicts | Each `AUTHSOME_HOME` starts its own daemon on its own ephemeral port. Confirm with `lsof -i`. | -## What's next - - - - How profiles work and how to create them. - - - Pick the right isolation level. - - diff --git a/docs/site/integrations/agents/hermes.mdx b/docs/site/integrations/agents/hermes.mdx index 47a18347..615236e7 100644 --- a/docs/site/integrations/agents/hermes.mdx +++ b/docs/site/integrations/agents/hermes.mdx @@ -111,7 +111,7 @@ Read it back: Use the work GitHub connection to open a PR. ``` -Hermes runs the agent under `authsome run --connection work -- ...`. For the underlying model, see [Profiles vs connections](/concepts/profiles-vs-connections). +Hermes runs the agent under `authsome run --connection work -- ...`. ## Without the skill @@ -136,7 +136,7 @@ See [Run agents with the proxy](/guides/run-agents-with-proxy) and [Python libra - **It cannot type a `client_secret` for you.** Authsome refuses sensitive values as command-line arguments. The browser bridge is the only path. - **It cannot bypass the proxy CA requirement.** HTTPS interception needs the mitmproxy CA trusted on the machine. The first `authsome run` writes the CA to `~/.mitmproxy/`. See [Proxy networking](/troubleshooting/proxy-networking). -- **It cannot share the vault across machines.** Hermes on a second machine has its own `~/.authsome/`. Re-run logins there, or use a hosted daemon (see [Hosted deployment model](/security/hosted-deployment)). +- **It cannot share the vault across machines.** Hermes on a second machine has its own `~/.authsome/`. Re-run logins there. ## Troubleshooting diff --git a/docs/site/integrations/api-key/ahrefs.mdx b/docs/site/integrations/api-key/ahrefs.mdx index 51e76170..6fd683d6 100644 --- a/docs/site/integrations/api-key/ahrefs.mdx +++ b/docs/site/integrations/api-key/ahrefs.mdx @@ -97,7 +97,6 @@ authsome inspect ahrefs > ~/.authsome/providers/ahrefs.json authsome list # source now shows "custom" for ahrefs ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/apollo.mdx b/docs/site/integrations/api-key/apollo.mdx index 43baa68b..8b955ac2 100644 --- a/docs/site/integrations/api-key/apollo.mdx +++ b/docs/site/integrations/api-key/apollo.mdx @@ -97,7 +97,6 @@ authsome inspect apollo > ~/.authsome/providers/apollo.json authsome list # source now shows "custom" for apollo ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/ashby.mdx b/docs/site/integrations/api-key/ashby.mdx index 7dbda1d6..9b8f38cb 100644 --- a/docs/site/integrations/api-key/ashby.mdx +++ b/docs/site/integrations/api-key/ashby.mdx @@ -97,7 +97,6 @@ authsome inspect ashby > ~/.authsome/providers/ashby.json authsome list # source now shows "custom" for ashby ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/beehiiv.mdx b/docs/site/integrations/api-key/beehiiv.mdx index d72d1d80..a7dff983 100644 --- a/docs/site/integrations/api-key/beehiiv.mdx +++ b/docs/site/integrations/api-key/beehiiv.mdx @@ -97,7 +97,6 @@ authsome inspect beehiiv > ~/.authsome/providers/beehiiv.json authsome list # source now shows "custom" for beehiiv ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/brevo.mdx b/docs/site/integrations/api-key/brevo.mdx index c544f006..ca5f208b 100644 --- a/docs/site/integrations/api-key/brevo.mdx +++ b/docs/site/integrations/api-key/brevo.mdx @@ -97,7 +97,6 @@ authsome inspect brevo > ~/.authsome/providers/brevo.json authsome list # source now shows "custom" for brevo ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/buffer.mdx b/docs/site/integrations/api-key/buffer.mdx index 4caa9b95..dd88ad9b 100644 --- a/docs/site/integrations/api-key/buffer.mdx +++ b/docs/site/integrations/api-key/buffer.mdx @@ -98,7 +98,6 @@ authsome inspect buffer > ~/.authsome/providers/buffer.json authsome list # source now shows "custom" for buffer ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/calendly.mdx b/docs/site/integrations/api-key/calendly.mdx index 97770c46..209800ce 100644 --- a/docs/site/integrations/api-key/calendly.mdx +++ b/docs/site/integrations/api-key/calendly.mdx @@ -97,7 +97,6 @@ authsome inspect calendly > ~/.authsome/providers/calendly.json authsome list # source now shows "custom" for calendly ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/clearbit.mdx b/docs/site/integrations/api-key/clearbit.mdx index 007001e9..9ec2d675 100644 --- a/docs/site/integrations/api-key/clearbit.mdx +++ b/docs/site/integrations/api-key/clearbit.mdx @@ -99,7 +99,6 @@ authsome inspect clearbit > ~/.authsome/providers/clearbit.json authsome list # source now shows "custom" for clearbit ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/dub.mdx b/docs/site/integrations/api-key/dub.mdx index ac4c8c71..3911c102 100644 --- a/docs/site/integrations/api-key/dub.mdx +++ b/docs/site/integrations/api-key/dub.mdx @@ -97,7 +97,6 @@ authsome inspect dub > ~/.authsome/providers/dub.json authsome list # source now shows "custom" for dub ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/g2.mdx b/docs/site/integrations/api-key/g2.mdx index 3ecfaad9..a113e28e 100644 --- a/docs/site/integrations/api-key/g2.mdx +++ b/docs/site/integrations/api-key/g2.mdx @@ -99,7 +99,6 @@ authsome inspect g2 > ~/.authsome/providers/g2.json authsome list # source now shows "custom" for g2 ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/hunter.mdx b/docs/site/integrations/api-key/hunter.mdx index 17e59b9e..ce0c6841 100644 --- a/docs/site/integrations/api-key/hunter.mdx +++ b/docs/site/integrations/api-key/hunter.mdx @@ -97,7 +97,6 @@ authsome inspect hunter > ~/.authsome/providers/hunter.json authsome list # source now shows "custom" for hunter ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/index.mdx b/docs/site/integrations/api-key/index.mdx index e726c1d8..7b5d5bdf 100644 --- a/docs/site/integrations/api-key/index.mdx +++ b/docs/site/integrations/api-key/index.mdx @@ -109,7 +109,6 @@ For a service authsome doesn't bundle (Anthropic, Stripe, Twilio, and many other 2. Write a JSON definition. See [Custom providers](/guides/custom-providers) for the template. 3. `authsome register ./.json`. -The schema is in [Provider schema](/reference/provider-schema). ## What's next diff --git a/docs/site/integrations/api-key/instantly.mdx b/docs/site/integrations/api-key/instantly.mdx index 5b3e842e..c22d075d 100644 --- a/docs/site/integrations/api-key/instantly.mdx +++ b/docs/site/integrations/api-key/instantly.mdx @@ -97,7 +97,6 @@ authsome inspect instantly > ~/.authsome/providers/instantly.json authsome list # source now shows "custom" for instantly ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/intercom.mdx b/docs/site/integrations/api-key/intercom.mdx index 6839c26d..aafe8da2 100644 --- a/docs/site/integrations/api-key/intercom.mdx +++ b/docs/site/integrations/api-key/intercom.mdx @@ -100,7 +100,6 @@ authsome inspect intercom > ~/.authsome/providers/intercom.json authsome list # source now shows "custom" for intercom ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/keywords-everywhere.mdx b/docs/site/integrations/api-key/keywords-everywhere.mdx index ed754d89..1270fd92 100644 --- a/docs/site/integrations/api-key/keywords-everywhere.mdx +++ b/docs/site/integrations/api-key/keywords-everywhere.mdx @@ -97,7 +97,6 @@ authsome inspect keywords-everywhere > ~/.authsome/providers/keywords-everywhere authsome list # source now shows "custom" for keywords-everywhere ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/klaviyo.mdx b/docs/site/integrations/api-key/klaviyo.mdx index 2699b57e..d82fa7fe 100644 --- a/docs/site/integrations/api-key/klaviyo.mdx +++ b/docs/site/integrations/api-key/klaviyo.mdx @@ -97,7 +97,6 @@ authsome inspect klaviyo > ~/.authsome/providers/klaviyo.json authsome list # source now shows "custom" for klaviyo ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/lemlist.mdx b/docs/site/integrations/api-key/lemlist.mdx index a9c18464..c1a93a14 100644 --- a/docs/site/integrations/api-key/lemlist.mdx +++ b/docs/site/integrations/api-key/lemlist.mdx @@ -97,7 +97,6 @@ authsome inspect lemlist > ~/.authsome/providers/lemlist.json authsome list # source now shows "custom" for lemlist ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/livestorm.mdx b/docs/site/integrations/api-key/livestorm.mdx index 7079007f..38e97605 100644 --- a/docs/site/integrations/api-key/livestorm.mdx +++ b/docs/site/integrations/api-key/livestorm.mdx @@ -97,7 +97,6 @@ authsome inspect livestorm > ~/.authsome/providers/livestorm.json authsome list # source now shows "custom" for livestorm ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/mailchimp.mdx b/docs/site/integrations/api-key/mailchimp.mdx index f3199167..f70cc05f 100644 --- a/docs/site/integrations/api-key/mailchimp.mdx +++ b/docs/site/integrations/api-key/mailchimp.mdx @@ -100,7 +100,6 @@ authsome inspect mailchimp > ~/.authsome/providers/mailchimp.json authsome list # source now shows "custom" for mailchimp ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/mention-me.mdx b/docs/site/integrations/api-key/mention-me.mdx index 0bce7d53..674c79ee 100644 --- a/docs/site/integrations/api-key/mention-me.mdx +++ b/docs/site/integrations/api-key/mention-me.mdx @@ -99,7 +99,6 @@ authsome inspect mention-me > ~/.authsome/providers/mention-me.json authsome list # source now shows "custom" for mention-me ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/openai.mdx b/docs/site/integrations/api-key/openai.mdx index bbbe7989..01fc88cc 100644 --- a/docs/site/integrations/api-key/openai.mdx +++ b/docs/site/integrations/api-key/openai.mdx @@ -137,7 +137,7 @@ authsome inspect openai > ~/.authsome/providers/openai.json authsome list # openai now shows source=custom ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema) for every field. +User-registered files always win over bundled definitions. ## Troubleshooting diff --git a/docs/site/integrations/api-key/optimizely.mdx b/docs/site/integrations/api-key/optimizely.mdx index 21425ab2..36f283ae 100644 --- a/docs/site/integrations/api-key/optimizely.mdx +++ b/docs/site/integrations/api-key/optimizely.mdx @@ -97,7 +97,6 @@ authsome inspect optimizely > ~/.authsome/providers/optimizely.json authsome list # source now shows "custom" for optimizely ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/postmark.mdx b/docs/site/integrations/api-key/postmark.mdx index 27ca13fe..98657520 100644 --- a/docs/site/integrations/api-key/postmark.mdx +++ b/docs/site/integrations/api-key/postmark.mdx @@ -97,7 +97,6 @@ authsome inspect postmark > ~/.authsome/providers/postmark.json authsome list # source now shows "custom" for postmark ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/resend.mdx b/docs/site/integrations/api-key/resend.mdx index d9343bcc..6f0cd462 100644 --- a/docs/site/integrations/api-key/resend.mdx +++ b/docs/site/integrations/api-key/resend.mdx @@ -97,7 +97,6 @@ authsome inspect resend > ~/.authsome/providers/resend.json authsome list # source now shows "custom" for resend ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/rewardful.mdx b/docs/site/integrations/api-key/rewardful.mdx index 900a92cf..ca9ec4a4 100644 --- a/docs/site/integrations/api-key/rewardful.mdx +++ b/docs/site/integrations/api-key/rewardful.mdx @@ -97,7 +97,6 @@ authsome inspect rewardful > ~/.authsome/providers/rewardful.json authsome list # source now shows "custom" for rewardful ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/savvycal.mdx b/docs/site/integrations/api-key/savvycal.mdx index 9059711a..41300c81 100644 --- a/docs/site/integrations/api-key/savvycal.mdx +++ b/docs/site/integrations/api-key/savvycal.mdx @@ -97,7 +97,6 @@ authsome inspect savvycal > ~/.authsome/providers/savvycal.json authsome list # source now shows "custom" for savvycal ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/semrush.mdx b/docs/site/integrations/api-key/semrush.mdx index 9f58efde..9910a9ce 100644 --- a/docs/site/integrations/api-key/semrush.mdx +++ b/docs/site/integrations/api-key/semrush.mdx @@ -97,7 +97,6 @@ authsome inspect semrush > ~/.authsome/providers/semrush.json authsome list # source now shows "custom" for semrush ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/sendgrid.mdx b/docs/site/integrations/api-key/sendgrid.mdx index ec8cbddf..61ade9f2 100644 --- a/docs/site/integrations/api-key/sendgrid.mdx +++ b/docs/site/integrations/api-key/sendgrid.mdx @@ -98,7 +98,6 @@ authsome inspect sendgrid > ~/.authsome/providers/sendgrid.json authsome list # source now shows "custom" for sendgrid ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/tolt.mdx b/docs/site/integrations/api-key/tolt.mdx index ea5f1261..fbb708d0 100644 --- a/docs/site/integrations/api-key/tolt.mdx +++ b/docs/site/integrations/api-key/tolt.mdx @@ -97,7 +97,6 @@ authsome inspect tolt > ~/.authsome/providers/tolt.json authsome list # source now shows "custom" for tolt ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/typeform.mdx b/docs/site/integrations/api-key/typeform.mdx index 990c0a62..b531f4b2 100644 --- a/docs/site/integrations/api-key/typeform.mdx +++ b/docs/site/integrations/api-key/typeform.mdx @@ -97,7 +97,6 @@ authsome inspect typeform > ~/.authsome/providers/typeform.json authsome list # source now shows "custom" for typeform ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/wistia.mdx b/docs/site/integrations/api-key/wistia.mdx index ae8b87b4..ba81cd2d 100644 --- a/docs/site/integrations/api-key/wistia.mdx +++ b/docs/site/integrations/api-key/wistia.mdx @@ -99,7 +99,6 @@ authsome inspect wistia > ~/.authsome/providers/wistia.json authsome list # source now shows "custom" for wistia ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/api-key/zapier.mdx b/docs/site/integrations/api-key/zapier.mdx index 222182f1..f904e580 100644 --- a/docs/site/integrations/api-key/zapier.mdx +++ b/docs/site/integrations/api-key/zapier.mdx @@ -97,7 +97,6 @@ authsome inspect zapier > ~/.authsome/providers/zapier.json authsome list # source now shows "custom" for zapier ``` -See [Provider schema](/reference/provider-schema) for every field. ## What's next diff --git a/docs/site/integrations/oauth/atlassian.mdx b/docs/site/integrations/oauth/atlassian.mdx index aa518557..d68dafcb 100644 --- a/docs/site/integrations/oauth/atlassian.mdx +++ b/docs/site/integrations/oauth/atlassian.mdx @@ -108,7 +108,7 @@ authsome inspect atlassian > ~/.authsome/providers/atlassian.json authsome list # source now shows "custom" for atlassian ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/discord.mdx b/docs/site/integrations/oauth/discord.mdx index 3675eb36..3e4e9b4a 100644 --- a/docs/site/integrations/oauth/discord.mdx +++ b/docs/site/integrations/oauth/discord.mdx @@ -112,7 +112,7 @@ authsome inspect discord > ~/.authsome/providers/discord.json authsome list # source now shows "custom" for discord ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/github.mdx b/docs/site/integrations/oauth/github.mdx index 28fd0673..03f2e7c9 100644 --- a/docs/site/integrations/oauth/github.mdx +++ b/docs/site/integrations/oauth/github.mdx @@ -199,7 +199,7 @@ authsome inspect github > ~/.authsome/providers/github.json authsome list # source now shows "custom" for github ``` -The schema is documented in [Provider schema](/reference/provider-schema). + ## Troubleshooting diff --git a/docs/site/integrations/oauth/gitlab.mdx b/docs/site/integrations/oauth/gitlab.mdx index 4f1e28eb..0170e48b 100644 --- a/docs/site/integrations/oauth/gitlab.mdx +++ b/docs/site/integrations/oauth/gitlab.mdx @@ -118,7 +118,7 @@ authsome inspect gitlab > ~/.authsome/providers/gitlab.json authsome list # source now shows "custom" for gitlab ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/google.mdx b/docs/site/integrations/oauth/google.mdx index 3df234d3..d2cbc0b8 100644 --- a/docs/site/integrations/oauth/google.mdx +++ b/docs/site/integrations/oauth/google.mdx @@ -118,7 +118,7 @@ authsome inspect google > ~/.authsome/providers/google.json authsome list # source now shows "custom" for google ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/hubspot.mdx b/docs/site/integrations/oauth/hubspot.mdx index 3264b494..b11477d4 100644 --- a/docs/site/integrations/oauth/hubspot.mdx +++ b/docs/site/integrations/oauth/hubspot.mdx @@ -112,7 +112,7 @@ authsome inspect hubspot > ~/.authsome/providers/hubspot.json authsome list # source now shows "custom" for hubspot ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/index.mdx b/docs/site/integrations/oauth/index.mdx index 88219b90..08faa8a8 100644 --- a/docs/site/integrations/oauth/index.mdx +++ b/docs/site/integrations/oauth/index.mdx @@ -57,7 +57,7 @@ If authsome doesn't bundle the provider you need: 2. Write a JSON definition. See [Custom providers](/guides/custom-providers) for templates. 3. `authsome register ./.json`. -The JSON schema is in [Provider schema](/reference/provider-schema). + ## What's next diff --git a/docs/site/integrations/oauth/klaviyo-oauth.mdx b/docs/site/integrations/oauth/klaviyo-oauth.mdx index d9527251..d58015e1 100644 --- a/docs/site/integrations/oauth/klaviyo-oauth.mdx +++ b/docs/site/integrations/oauth/klaviyo-oauth.mdx @@ -108,7 +108,7 @@ authsome inspect klaviyo-oauth > ~/.authsome/providers/klaviyo-oauth.json authsome list # source now shows "custom" for klaviyo-oauth ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/linear.mdx b/docs/site/integrations/oauth/linear.mdx index a47c6c63..575c86ec 100644 --- a/docs/site/integrations/oauth/linear.mdx +++ b/docs/site/integrations/oauth/linear.mdx @@ -107,7 +107,7 @@ authsome inspect linear > ~/.authsome/providers/linear.json authsome list # source now shows "custom" for linear ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/microsoft.mdx b/docs/site/integrations/oauth/microsoft.mdx index ad2a7d56..c255e2c0 100644 --- a/docs/site/integrations/oauth/microsoft.mdx +++ b/docs/site/integrations/oauth/microsoft.mdx @@ -118,7 +118,7 @@ authsome inspect microsoft > ~/.authsome/providers/microsoft.json authsome list # source now shows "custom" for microsoft ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/notion-dcr.mdx b/docs/site/integrations/oauth/notion-dcr.mdx index b20304af..50d66d82 100644 --- a/docs/site/integrations/oauth/notion-dcr.mdx +++ b/docs/site/integrations/oauth/notion-dcr.mdx @@ -88,7 +88,7 @@ authsome inspect notion_dcr > ~/.authsome/providers/notion_dcr.json authsome list # source now shows "custom" for notion_dcr ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/notion.mdx b/docs/site/integrations/oauth/notion.mdx index 1f8dbcbb..b15f0998 100644 --- a/docs/site/integrations/oauth/notion.mdx +++ b/docs/site/integrations/oauth/notion.mdx @@ -102,7 +102,7 @@ authsome inspect notion > ~/.authsome/providers/notion.json authsome list # source now shows "custom" for notion ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/postiz.mdx b/docs/site/integrations/oauth/postiz.mdx index 5f4731f2..95d20d7a 100644 --- a/docs/site/integrations/oauth/postiz.mdx +++ b/docs/site/integrations/oauth/postiz.mdx @@ -87,7 +87,7 @@ authsome inspect postiz > ~/.authsome/providers/postiz.json authsome list # source now shows "custom" for postiz ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/slack.mdx b/docs/site/integrations/oauth/slack.mdx index 93356e24..3b0e6fac 100644 --- a/docs/site/integrations/oauth/slack.mdx +++ b/docs/site/integrations/oauth/slack.mdx @@ -112,7 +112,7 @@ authsome inspect slack > ~/.authsome/providers/slack.json authsome list # source now shows "custom" for slack ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/integrations/oauth/x.mdx b/docs/site/integrations/oauth/x.mdx index f51488b3..39f5399a 100644 --- a/docs/site/integrations/oauth/x.mdx +++ b/docs/site/integrations/oauth/x.mdx @@ -108,7 +108,7 @@ authsome inspect x > ~/.authsome/providers/x.json authsome list # source now shows "custom" for x ``` -User-registered files always win over bundled definitions. See [Provider schema](/reference/provider-schema). +User-registered files always win over bundled definitions. ## What's next diff --git a/docs/site/reference/cli.mdx b/docs/site/reference/cli.mdx index 7219bae9..78cf8a17 100644 --- a/docs/site/reference/cli.mdx +++ b/docs/site/reference/cli.mdx @@ -160,7 +160,7 @@ authsome profile create # create a new local profile keypair authsome profile use # switch the active local profile ``` -Profiles are backed by Ed25519 identity keys at `~/.authsome/identities/`. Each profile has its own credential namespace in the vault. See [Profiles vs Connections](/concepts/profiles-vs-connections) for the distinction. +Profiles are backed by Ed25519 identity keys at `~/.authsome/identities/`. Each profile has its own credential namespace in the vault. ### `daemon` diff --git a/docs/site/reference/daemon-api.mdx b/docs/site/reference/daemon-api.mdx index c183dc3e..00d385fb 100644 --- a/docs/site/reference/daemon-api.mdx +++ b/docs/site/reference/daemon-api.mdx @@ -127,7 +127,7 @@ CRUD for connection records. Behind the scenes, every route goes through the Aut |--------|------|---------| | `GET` | `/providers` | List every bundled and custom provider with auth type, flow, and source. | | `GET` | `/providers/{provider}` | Full provider definition. | -| `POST` | `/providers` | Register a custom provider definition (the body is the JSON in [Provider schema](/reference/provider-schema)). | +| `POST` | `/providers` | Register a custom provider definition. | | `DELETE` | `/providers/{provider}` | Remove a custom provider or reset a bundled one to its shipped form. | ## Proxy resolution @@ -141,19 +141,18 @@ These routes back the local HTTP proxy started by `authsome run`. The proxy neve ## Dashboard UI -These routes serve HTML. The dashboard is served at the root prefix. +The Next.js dashboard is served as a static export at the daemon root. Browser-only form routes remain for account sessions and provider login starts. | Method | Path | Purpose | |--------|------|---------| -| `GET` | `/` | Dashboard overview. | -| `GET` | `/applications` | Applications / Providers view. | -| `GET` | `/manage/connections` | All connections, by provider. | -| `GET` | `/identity` | Identity details. | -| `GET` | `/audit` | Admin audit dashboard. | -| `GET` | `/apps/{provider_name}` | Detail pane for one provider. | -| `GET` | `/apps/{provider_name}/connections/{connection_name}` | Connection detail pane. | -| `POST` | `/apps/{provider_name}/{connection_name}/disconnect` | Log out from the dashboard UI. | -| `POST` | `/apps/{provider_name}/connect` | Start a flow from the dashboard UI. | +| `GET` | `/` | Static dashboard shell. | +| `POST` | `/session` | Return the dashboard URL for a PoP-authenticated local client. | +| `POST` | `/auth/login` | Create a browser dashboard session. | +| `POST` | `/auth/register` | Register an account and create a browser dashboard session. | +| `POST` | `/logout` | Clear the browser dashboard session. | +| `GET` | `/claim/{token}` | Render the account claim confirmation page. | +| `POST` | `/claim/{token}/confirm` | Attach a local identity to the signed-in account. | +| `POST` | `/auth/providers/{provider_name}/connect` | Start a provider login flow from the dashboard. | ## Auth diff --git a/docs/site/reference/python-library.mdx b/docs/site/reference/python-library.mdx index a6b0b26b..734eeffd 100644 --- a/docs/site/reference/python-library.mdx +++ b/docs/site/reference/python-library.mdx @@ -173,7 +173,7 @@ definition = ProviderDefinition.model_validate_json(open("acmecrm.json").read()) auth.register_provider(definition, force=False) ``` -Equivalent to `authsome register ./acmecrm.json`. The schema is documented in [Provider schema](/reference/provider-schema). +Equivalent to `authsome register ./acmecrm.json`. ## AuthLayer diff --git a/docs/site/snippets/whats-next-apikey.mdx b/docs/site/snippets/whats-next-apikey.mdx index cd82843b..ddc208fd 100644 --- a/docs/site/snippets/whats-next-apikey.mdx +++ b/docs/site/snippets/whats-next-apikey.mdx @@ -8,7 +8,4 @@ All bundled API-key providers. - - Every field in a provider definition. - diff --git a/docs/site/snippets/whats-next-oauth.mdx b/docs/site/snippets/whats-next-oauth.mdx index 190e2606..4810e1b5 100644 --- a/docs/site/snippets/whats-next-oauth.mdx +++ b/docs/site/snippets/whats-next-oauth.mdx @@ -5,9 +5,6 @@ Keep two or more accounts on the same provider side by side. - - Every field in a provider definition. - All bundled OAuth providers. diff --git a/docs/site/troubleshooting/daemon-issues.mdx b/docs/site/troubleshooting/daemon-issues.mdx index 0dd66213..65da5bdf 100644 --- a/docs/site/troubleshooting/daemon-issues.mdx +++ b/docs/site/troubleshooting/daemon-issues.mdx @@ -75,7 +75,6 @@ If you do want a hosted daemon and it's unreachable, the fix is on the daemon ho - `AUTHSOME_SERVER_BASE_URL` on the daemon matches the URL you're calling. - Ingress is allowed from your client's network (private VPC, VPN, or whatever the team uses). -See [Hosted deployment model](/security/hosted-deployment). ## Login was interrupted by a daemon restart diff --git a/docs/site/troubleshooting/doctor.mdx b/docs/site/troubleshooting/doctor.mdx index fb3d4560..897510fb 100644 --- a/docs/site/troubleshooting/doctor.mdx +++ b/docs/site/troubleshooting/doctor.mdx @@ -53,7 +53,7 @@ A healthy machine prints `OK` for each check and exits with code `0`. [FAIL] Provider 'acmecrm' failed to parse: missing required field 'token_url' ``` - A custom provider JSON in `~/.authsome/providers/` is malformed. Open the file, fix the indicated field, and re-run. See the [Provider schema](/reference/provider-schema) for required fields per auth type. + A custom provider JSON in `~/.authsome/providers/` is malformed. Open the file, fix the indicated field, and re-run. diff --git a/evals/.gitignore b/evals/.gitignore deleted file mode 100644 index 74163ecb..00000000 --- a/evals/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -tmp/ -results/ diff --git a/pyproject.toml b/pyproject.toml index b99c05f5..d0649019 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ dependencies = [ "fastapi>=0.115", "uvicorn>=0.30", "python-multipart>=0.0.27", - "jinja2>=3.1", "py-key-value-aio[disk]", "aiosqlite>=0.20", "asyncpg>=0.30", @@ -65,6 +64,9 @@ authsome = "authsome.cli.main:cli" [tool.hatch.build.targets.wheel] packages = ["src/authsome"] +[tool.hatch.build.targets.sdist.force-include] +"src/authsome/ui/web" = "src/authsome/ui/web" + [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["src"] diff --git a/scripts/build-ui.sh b/scripts/build-ui.sh new file mode 100755 index 00000000..4b106982 --- /dev/null +++ b/scripts/build-ui.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +UI_DIR="${ROOT_DIR}/ui" +TARGET_DIR="${ROOT_DIR}/src/authsome/ui/web" +UV_BIN="${UV:-uv}" + +"${UV_BIN}" run pnpm --dir "${UI_DIR}" install --frozen-lockfile +"${UV_BIN}" run pnpm --dir "${UI_DIR}" build + +rm -rf "${TARGET_DIR}" +mkdir -p "${TARGET_DIR}" +cp -R "${UI_DIR}/out/." "${TARGET_DIR}/" +printf '%s\n' "Generated static UI assets are copied here by scripts/build-ui.sh." > "${TARGET_DIR}/.gitkeep" diff --git a/evals/evals.json b/skills/authsome/evals/evals.json similarity index 100% rename from evals/evals.json rename to skills/authsome/evals/evals.json diff --git a/evals/generate_report.py b/skills/authsome/evals/generate_report.py similarity index 100% rename from evals/generate_report.py rename to skills/authsome/evals/generate_report.py diff --git a/src/authsome/proxy/server.py b/src/authsome/proxy/server.py index 4f3cbafd..6d637e50 100644 --- a/src/authsome/proxy/server.py +++ b/src/authsome/proxy/server.py @@ -520,15 +520,14 @@ def _deny_body(reason: str, match: RouteMatch | None) -> str: and a dashboard URL so the agent (or human) can recover; other reasons fall back to a generic message. - The dashboard URL uses ``DEFAULT_SERVER_BASE_URL``. It still requires an active dashboard session - to land on the connect screen directly. + The dashboard URL uses ``DEFAULT_SERVER_BASE_URL`` and still requires an active browser session. """ if reason == "no_credentials" and match is not None: provider = match.provider return ( f"Forbidden: provider '{provider}' is configured but has no " f"active connection. Run `authsome login {provider}` to connect, " - f"or visit {DEFAULT_SERVER_BASE_URL}/apps/{provider}." + f"or open the dashboard at {DEFAULT_SERVER_BASE_URL}/." ) return "Forbidden by Authsome proxy policy" diff --git a/src/authsome/server/app.py b/src/authsome/server/app.py index 7d9316da..9eb3081e 100644 --- a/src/authsome/server/app.py +++ b/src/authsome/server/app.py @@ -105,7 +105,7 @@ def ui_auth_required_handler(request: Request, exc: UiAuthRequiredError): app.include_router(proxy_router) app.include_router(ui_router) - static_dir = files("authsome.ui").joinpath("static") - app.mount("/static", StaticFiles(directory=str(static_dir)), name="ui-static") + ui_dir = files("authsome.ui").joinpath("web") + app.mount("/", StaticFiles(directory=str(ui_dir), html=True, check_dir=False), name="ui") return app diff --git a/src/authsome/server/routes/_deps.py b/src/authsome/server/routes/_deps.py index 7aeab48e..e420f783 100644 --- a/src/authsome/server/routes/_deps.py +++ b/src/authsome/server/routes/_deps.py @@ -179,6 +179,29 @@ async def get_admin_auth_service( return auth +async def get_daemon_or_browser_auth_service(request: Request) -> CredentialService: + """Resolve auth from PoP headers or an existing browser dashboard session.""" + if request.headers.get("Authorization"): + ownership = await verify_pop_caller(request) + return _build_service(request, ownership) + + await resolve_ui_request_identity(request) + auth = await get_auth_service( + request, + principal_id=getattr(request.state, "ui_principal_id", None), + ) + if auth is None: + raise HTTPException(status_code=401, detail="Missing or invalid browser session") + return auth + + +async def get_admin_daemon_or_browser_auth_service(request: Request) -> CredentialService: + auth = await get_daemon_or_browser_auth_service(request) + if auth.principal_role != PrincipalRole.ADMIN: + raise HTTPException(status_code=403, detail="Admin role required") + return auth + + def get_vault_registry(request: Request) -> VaultRegistry: return request.app.state.store.vaults diff --git a/src/authsome/server/routes/audit.py b/src/authsome/server/routes/audit.py index 215b3abf..70684c89 100644 --- a/src/authsome/server/routes/audit.py +++ b/src/authsome/server/routes/audit.py @@ -8,7 +8,10 @@ from authsome import audit from authsome.server.credential_service import CredentialService -from authsome.server.routes._deps import get_admin_auth_service, get_protected_auth_service +from authsome.server.routes._deps import ( + get_admin_daemon_or_browser_auth_service, + get_protected_auth_service, +) router = APIRouter(prefix="/audit", tags=["audit"]) @@ -17,7 +20,7 @@ async def list_audit_events( request: Request, limit: int = 50, - auth: CredentialService = Depends(get_admin_auth_service), + auth: CredentialService = Depends(get_admin_daemon_or_browser_auth_service), ) -> dict[str, Any]: _ = auth return {"entries": await request.app.state.audit_log.list_events(limit=limit)} diff --git a/src/authsome/server/routes/connections.py b/src/authsome/server/routes/connections.py index ec9edf10..287fbd06 100644 --- a/src/authsome/server/routes/connections.py +++ b/src/authsome/server/routes/connections.py @@ -7,14 +7,19 @@ from authsome.auth.models.enums import ExportFormat from authsome.server.analytics import capture_event from authsome.server.credential_service import CredentialService -from authsome.server.routes._deps import get_admin_auth_service, get_protected_auth_service, get_vault_registry +from authsome.server.routes._deps import ( + get_admin_auth_service, + get_daemon_or_browser_auth_service, + get_protected_auth_service, + get_vault_registry, +) from authsome.server.store.repositories import VaultRegistry router = APIRouter(tags=["connections"]) @router.get("/connections") -async def list_connections(auth: CredentialService = Depends(get_protected_auth_service)): +async def list_connections(auth: CredentialService = Depends(get_daemon_or_browser_auth_service)): by_source = await auth.list_providers_by_source() return { "connections": await auth.list_connections(), diff --git a/src/authsome/server/routes/health.py b/src/authsome/server/routes/health.py index 1cd3f99d..5638a9e3 100644 --- a/src/authsome/server/routes/health.py +++ b/src/authsome/server/routes/health.py @@ -6,7 +6,11 @@ from authsome import __version__ from authsome.server.credential_service import CredentialService -from authsome.server.routes._deps import get_protected_auth_service, get_server_base_url +from authsome.server.routes._deps import ( + get_daemon_or_browser_auth_service, + get_protected_auth_service, + get_server_base_url, +) from authsome.server.schemas import HealthResponse, ReadyResponse from authsome.utils import connection_is_active @@ -115,18 +119,20 @@ async def ready( @router.get("/whoami") async def whoami( request: Request, - auth: CredentialService = Depends(get_protected_auth_service), + auth: CredentialService = Depends(get_daemon_or_browser_auth_service), server_base_url: str = Depends(get_server_base_url), ) -> dict[str, str]: effective_source, backend_description = _describe_vault_encryption(auth.vault) - identity = auth.require_identity() + identity = auth.identity or getattr(request.state, "ui_identity", "") or "" return { "version": __version__, "home": str(request.app.state.store.home), "identity": identity, "active_identity": identity, - "principal_id": getattr(request.state, "principal_id", ""), - "vault_id": getattr(request.state, "vault_id", ""), + "principal_id": getattr(request.state, "principal_id", None) or auth.principal_id or "", + "vault_id": getattr(request.state, "vault_id", None) or auth.vault_id or "", + "principal_role": auth.principal_role.value, + "account_email": getattr(request.state, "ui_email", ""), "did": getattr(request.state, "did", ""), "registration_status": getattr(request.state, "registration_status", "registered"), "daemon_url": server_base_url, diff --git a/src/authsome/server/routes/ui.py b/src/authsome/server/routes/ui.py index 1e784941..9279ed1b 100644 --- a/src/authsome/server/routes/ui.py +++ b/src/authsome/server/routes/ui.py @@ -1,32 +1,19 @@ -"""HTML routes that render the Authsome local dashboard. - -The UI is intentionally server-rendered against the same FastAPI app that -serves the JSON daemon API. This keeps it a single process on port 7998 -and avoids a separate static server. -""" +"""Browser-session routes for the Authsome local dashboard.""" from __future__ import annotations from collections.abc import Awaitable, Callable -from datetime import UTC, datetime -from importlib.resources import files from typing import Any from urllib.parse import urlencode from fastapi import APIRouter, BackgroundTasks, Depends, Request, Response from fastapi.responses import HTMLResponse, RedirectResponse -from fastapi.templating import Jinja2Templates -from authsome import __version__ -from authsome.auth.models.connection import ConnectionRecord, ProviderClientRecord -from authsome.auth.models.enums import AuthType, FlowType -from authsome.auth.models.provider import ProviderDefinition +from authsome.auth.models.enums import FlowType from authsome.auth.sessions import AuthSessionStore -from authsome.identity.principal import PrincipalRole from authsome.server.credential_service import CredentialService from authsome.server.routes._deps import ( UI_SESSION_COOKIE_NAME, - build_auth_service, get_auth_service, get_auth_sessions, get_protected_auth_service, @@ -42,35 +29,14 @@ router = APIRouter(tags=["ui"], include_in_schema=False) -# Templates ship inside the installed package alongside the code. -_TEMPLATES_DIR = files("authsome.ui").joinpath("templates") -templates = Jinja2Templates(directory=str(_TEMPLATES_DIR)) - def _redirect(request: Request, url: str) -> Response: - """Redirect normally, or via htmx full-page redirect for boosted forms.""" + """Redirect normally, or via htmx full-page redirect for form compatibility.""" if request.headers.get("HX-Request") == "true": return Response(status_code=204, headers={"HX-Redirect": url}) return RedirectResponse(url=url, status_code=303) -def _ui_cookie_secure(server_base_url: str) -> bool: - return server_base_url.startswith("https://") - - -def _ui_policy(request: Request, auth: CredentialService | None = None) -> dict[str, Any]: - role = auth.principal_role if auth is not None else getattr(request.state, "ui_principal_role", None) - is_admin = role == PrincipalRole.ADMIN - return { - "is_admin": is_admin, - "role_label": role.value.title() if role else None, - "show_admin_sections": is_admin, - "show_provider_client_details": is_admin, - "provider_management_label": ("OAuth Application" if is_admin else "OAuth application managed by Authsome"), - "show_identity": True, - } - - class UiAuthRequiredError(Exception): """Raised when a UI route needs to return an auth-related response.""" @@ -79,17 +45,16 @@ def __init__(self, response: Response) -> None: async def _resolve_ui_auth(request: Request, *, next_url: str | None = None) -> CredentialService: - await resolve_ui_request_identity(request) + identity = await resolve_ui_request_identity(request) auth = await get_auth_service( request, + identity=identity, principal_id=getattr(request.state, "ui_principal_id", None), ) if auth is not None: return auth target = _account_auth_next_url(next_url or request.query_params.get("next") or request.url.path) - if request.method == "GET" and request.url.path == "/": - raise UiAuthRequiredError(_account_auth_page_response(request.app.state.ui_sessions, next_url=target)) raise UiAuthRequiredError(RedirectResponse(url=_account_auth_entry_url(target), status_code=303)) @@ -111,6 +76,10 @@ def _account_auth_entry_url(next_url: str = "/") -> str: return f"/?{urlencode({'next': _account_auth_next_url(next_url)})}" +def _ui_cookie_secure(server_base_url: str) -> bool: + return server_base_url.startswith("https://") + + def _set_ui_session_cookie( response: Response, token: str, @@ -166,522 +135,20 @@ def _account_auth_page_response( return HTMLResponse(page, status_code=400 if error else 200) -def _page_context( - request: Request, page: str, *, auth: CredentialService | None = None, **kwargs: Any -) -> dict[str, Any]: - return { - "page": page, - "version": __version__, - "ui_identity": getattr(request.state, "ui_identity", None), - "ui_email": getattr(request.state, "ui_email", None), - **_ui_policy(request, auth), - **kwargs, - } - - -def _format_relative(when: datetime | None) -> str | None: - """Return a compact "in 47 minutes" / "2 days ago" label.""" - if when is None: - return None - if when.tzinfo is None: - when = when.replace(tzinfo=UTC) - delta_seconds = int((when - datetime.now(UTC)).total_seconds()) - abs_seconds = abs(delta_seconds) - direction = "in" if delta_seconds >= 0 else "ago" - - if abs_seconds < 60: - amount, unit = abs_seconds, "second" - elif abs_seconds < 3600: - amount, unit = abs_seconds // 60, "minute" - elif abs_seconds < 86400: - amount, unit = abs_seconds // 3600, "hour" - else: - amount, unit = abs_seconds // 86400, "day" - - plural = "" if amount == 1 else "s" - return f"{direction} {amount} {unit}{plural}" if direction == "in" else f"{amount} {unit}{plural} ago" - - -def _format_audit_time(value: Any) -> str: - if not value: - return "-" - try: - parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00")) - except ValueError: - return str(value) - if parsed.tzinfo is None: - parsed = parsed.replace(tzinfo=UTC) - return parsed.astimezone(UTC).strftime("%Y-%m-%d %H:%M UTC") - - -def _humanize_audit_event(value: Any) -> str: - event = str(value or "audit_event").replace("_", " ").replace("-", " ").strip() - return event[:1].upper() + event[1:] if event else "Audit event" - - -def _audit_event_rows(entries: list[dict[str, Any]]) -> list[dict[str, Any]]: - known = { - "event_id", - "timestamp", - "event", - "source", - "principal_id", - "identity", - "provider", - "connection", - "status", - } - rows: list[dict[str, Any]] = [] - for entry in entries: - provider = entry.get("provider") - connection = entry.get("connection") - metadata = {key: value for key, value in entry.items() if key not in known and value is not None} - target = " / ".join(str(part) for part in (provider, connection) if part) or "Authsome" - rows.append( - { - "event_id": entry.get("event_id") or "-", - "time": _format_audit_time(entry.get("timestamp")), - "event": _humanize_audit_event(entry.get("event")), - "source": entry.get("source") or "internal", - "actor": entry.get("identity") or entry.get("principal_id") or "system", - "target": target, - "status": entry.get("status") or "-", - "metadata": metadata, - } - ) - return rows - - -def _admin_required_response(title: str, message: str) -> HTMLResponse: - return HTMLResponse(pages.message_page(title, message), status_code=403) - - -def _provider_status(provider_name: str, connection_summaries: list[dict[str, Any]]) -> str: - """Map a connection list to a single status string for the overview cards.""" - if not connection_summaries: - return "available" - statuses = {c.get("status") for c in connection_summaries} - if "error" in statuses or "expired" in statuses: - return "reauth" - return "connected" - - -def _logo_initial(name: str) -> str: - return (name[:1] or "?").upper() - - -def _provider_api_url_label(provider: ProviderDefinition) -> str: - urls = provider.api_urls() - if urls: - return ", ".join(urls) - return (provider.oauth.base_url if provider.oauth else None) or provider.name - - -def _provider_primary_api_url(provider: ProviderDefinition) -> str: - return provider.primary_api_url() or (provider.oauth.base_url if provider.oauth else None) or provider.name - - -def _build_provider_view( - provider: ProviderDefinition, - source: str, - connections: list[dict[str, Any]], -) -> dict[str, Any]: - return { - "name": provider.name, - "display_name": provider.display_name, - "auth_type": provider.auth_type.value, - "auth_type_label": "OAuth 2.0" if provider.auth_type == AuthType.OAUTH2 else "API Key", - "api_url": _provider_api_url_label(provider), - "description": (provider.metadata or {}).get("description", ""), - "source": source, - "logo_initial": _logo_initial(provider.display_name or provider.name), - "status": _provider_status(provider.name, connections), - "connections": connections, - "scope_count": len(connections[0].get("scopes") or []) if connections else 0, - } - - -async def _provider_connection_groups( - request: Request, - *, - identity: str | None, - principal_id: str | None, - provider_name: str, -) -> list[dict[str, Any]]: - if not principal_id: - return [] - - bindings = request.app.state.store.principal_vault_bindings - vaults = request.app.state.store.vaults - groups: list[dict[str, Any]] = [] - - for binding in await bindings.list_for_principal(principal_id): - scoped_auth = build_auth_service( - request, - identity=identity, - principal_id=principal_id, - vault_id=binding.vault_id, - ) - provider_connections = next( - (group["connections"] for group in await scoped_auth.list_connections() if group["name"] == provider_name), - [], - ) - if not provider_connections: - continue - - vault_record = await vaults.get(binding.vault_id) - group_items: list[dict[str, Any]] = [] - for connection in provider_connections: - record = await scoped_auth.get_connection(provider_name, connection["connection_name"]) - group_items.append( - { - "connection_name": connection["connection_name"], - "identity": record.identity, - "status": connection["status"], - "href": f"/apps/{provider_name}/connections/{connection['connection_name']}", - } - ) - - groups.append( - { - "vault_label": (vault_record.handle if vault_record else "default").replace("-", " ").title(), - "connections": group_items, - } - ) - - return groups - - -def _provider_page_context( - request: Request, - auth: CredentialService, - provider: ProviderDefinition, - api_url: str, - *, - grouped_connections: list[dict[str, Any]], - provider_client: ProviderClientRecord | None, - redirect_uri: str, - auth_url: str | None, - token_url: str | None, -) -> dict[str, Any]: - policy = _ui_policy(request, auth) - return _page_context( - request, - "applications", - auth=auth, - provider=provider, - connection=None, - grouped_connections=grouped_connections, - logo_initial=_logo_initial(provider.display_name or provider.name), - api_url=api_url, - auth_type_label="OAuth 2.0" if provider.auth_type == AuthType.OAUTH2 else "API Key", - client_id=provider_client.client_id if provider_client and policy["show_provider_client_details"] else None, - has_client_secret=bool( - provider_client and provider_client.client_secret and policy["show_provider_client_details"] - ), - redirect_uri=redirect_uri, - auth_url=auth_url, - token_url=token_url, - requires_named_login=any( - connection["connection_name"] == "default" - for group in grouped_connections - for connection in group["connections"] - ), - ) - - -def _connection_detail_context( - request: Request, - auth: CredentialService, - provider: ProviderDefinition, - connection_record: ConnectionRecord, - api_url: str, -) -> dict[str, Any]: - return _page_context( - request, - "connections", - auth=auth, - provider=provider, - connection=connection_record, - logo_initial=_logo_initial(provider.display_name or provider.name), - api_url=api_url, - expires_label=_format_relative(connection_record.expires_at), - obtained_label=_format_relative(connection_record.obtained_at), - scopes=connection_record.scopes or [], - ) - - -async def _all_provider_views(auth: CredentialService) -> list[dict[str, Any]]: - by_source = await auth.list_providers_by_source() - connections_by_provider = {group["name"]: group["connections"] for group in await auth.list_connections()} - views: list[dict[str, Any]] = [] - for source in ("bundled", "custom"): - for provider in by_source.get(source, []): - views.append(_build_provider_view(provider, source, connections_by_provider.get(provider.name, []))) - return views - - -def _build_connection_rows(providers: list[dict[str, Any]]) -> list[dict[str, Any]]: - rows: list[dict[str, Any]] = [] - for provider in providers: - for connection in provider["connections"]: - rows.append( - { - "provider_name": provider["name"], - "provider_display_name": provider["display_name"], - "connection_name": connection["connection_name"], - "status": connection["status"], - "auth_type_label": provider["auth_type_label"], - "href": f"/apps/{provider['name']}/connections/{connection['connection_name']}", - } - ) - return sorted(rows, key=lambda row: (row["provider_display_name"].lower(), row["connection_name"].lower())) - - -@router.get("/", response_class=HTMLResponse) -async def overview( - request: Request, - auth: CredentialService = Depends(require_ui_auth()), -) -> HTMLResponse: - providers = await _all_provider_views(auth) - connected = [p for p in providers if p["status"] != "available"] - available_count = len(providers) - len(connected) - oauth_count = sum(1 for p in connected if p["auth_type"] == AuthType.OAUTH2.value) - apikey_count = sum(1 for p in connected if p["auth_type"] == AuthType.API_KEY.value) - - # "Last activity" ~ most recent token issue/refresh time across connections. - last_activity_label: str | None = None - most_recent: datetime | None = None - for view in connected: - for conn in view["connections"]: - for ts_field in ("expires_at",): - ts = conn.get(ts_field) - if not ts: - continue - try: - parsed = datetime.fromisoformat(ts.replace("Z", "+00:00")) - except (TypeError, ValueError): - continue - if most_recent is None or parsed > most_recent: - most_recent = parsed - if most_recent is not None: - last_activity_label = _format_relative(most_recent) - - return templates.TemplateResponse( - request, - "overview.html", - _page_context( - request, - "overview", - auth=auth, - stats={ - "connected": len(connected), - "available": available_count, - "oauth": oauth_count, - "api_key": apikey_count, - }, - last_activity=last_activity_label or "—", - connected_providers=connected[:6], - ), - ) - - -@router.get("/applications", response_class=HTMLResponse) -async def applications( - request: Request, - auth: CredentialService = Depends(require_ui_auth("/applications")), -) -> Response: - providers = [ - { - **provider, - "requires_named_login": any( - connection["connection_name"] == "default" for connection in provider["connections"] - ), - } - for provider in await _all_provider_views(auth) - ] - return templates.TemplateResponse( - request, - "applications.html", - _page_context(request, "applications", auth=auth, providers=providers), - ) - - -@router.get("/manage/connections", response_class=HTMLResponse) -async def connections( - request: Request, - auth: CredentialService = Depends(require_ui_auth("/manage/connections")), -) -> Response: - providers = await _all_provider_views(auth) - rows = _build_connection_rows(providers) - return templates.TemplateResponse( - request, - "connections.html", - _page_context( - request, - "connections", - auth=auth, - connection_rows=rows, - total_connections=len(rows), - ), - ) - - -@router.get("/identity", response_class=HTMLResponse) -async def identity_page( - request: Request, - auth: CredentialService = Depends(require_ui_auth("/identity")), -) -> Response: - claims = await request.app.state.store.identity_claims.list_for_principal(request.state.ui_principal_id) - identities = [{"handle": claim.identity_handle, "is_active": False} for claim in claims] - return templates.TemplateResponse( - request, - "identity.html", - _page_context( - request, - "identity", - auth=auth, - identities=identities, - principal_id=auth.principal_id, - ), - ) - - -@router.get("/audit", response_class=HTMLResponse) -async def audit_page( - request: Request, - auth: CredentialService = Depends(require_ui_auth("/audit")), -) -> Response: - if auth.principal_role != PrincipalRole.ADMIN: - return _admin_required_response( - "Admin access required", - "Audit events are available only to administrators.", - ) - - entries = await request.app.state.audit_log.list_events(limit=100) - rows = _audit_event_rows(entries) - return templates.TemplateResponse( - request, - "audit.html", - _page_context( - request, - "audit", - auth=auth, - audit_events=rows, - audit_total=len(rows), - ), - ) - - -@router.get("/apps/{provider_name}", response_class=HTMLResponse) -async def app_detail( - provider_name: str, - request: Request, - auth: CredentialService = Depends(require_ui_auth()), - server_base_url: str = Depends(get_server_base_url), -) -> Response: - provider = await auth.get_provider(provider_name) - redirect_uri = build_callback_url(server_base_url) - api_url = _provider_api_url_label(provider) - policy = _ui_policy(request, auth) - if not policy["show_provider_client_details"]: - return templates.TemplateResponse( - request, - "app_detail_managed.html", - _page_context( - request, - "applications", - auth=auth, - provider=provider, - logo_initial=_logo_initial(provider.display_name or provider.name), - ), - ) - - client_record = await auth.get_provider_client(provider_name) - grouped_connections = await _provider_connection_groups( - request, - identity=auth.identity, - principal_id=auth.principal_id, - provider_name=provider_name, - ) - return templates.TemplateResponse( - request, - "app_provider.html", - _provider_page_context( - request, - auth, - provider, - api_url, - grouped_connections=grouped_connections, - provider_client=client_record, - redirect_uri=redirect_uri, - auth_url=provider.oauth.authorization_url if provider.oauth else None, - token_url=provider.oauth.token_url if provider.oauth else None, - ), - ) - - -@router.get("/apps/{provider_name}/connections/{connection_name}", response_class=HTMLResponse) -async def connection_detail( - provider_name: str, - connection_name: str, - request: Request, - auth: CredentialService = Depends(require_ui_auth()), -) -> Response: - provider = await auth.get_provider(provider_name) - connection_record = await auth.get_connection(provider_name, connection_name) - api_url = _provider_api_url_label(provider) - common = _connection_detail_context(request, auth, provider, connection_record, api_url) - - if provider.auth_type == AuthType.OAUTH2: - return templates.TemplateResponse( - request, - "app_detail_oauth.html", - { - **common, - "access_token": connection_record.access_token, - "refresh_token": connection_record.refresh_token, - }, - ) - - return templates.TemplateResponse( - request, - "app_detail_apikey.html", - { - **common, - "api_key": connection_record.api_key, - "base_url": connection_record.base_url - or (provider.oauth.base_url if provider.oauth else None) - or _provider_primary_api_url(provider), - }, - ) - - -@router.post("/apps/{provider_name}/{connection_name}/disconnect") -async def disconnect_app( - provider_name: str, - connection_name: str, - request: Request, - auth: CredentialService = Depends(require_ui_auth("/manage/connections")), -) -> Response: - """Disconnect a provider connection from the dashboard.""" - await auth.logout(provider_name, connection_name) - return _redirect(request, "/manage/connections") - - -@router.post("/apps/{provider_name}/connect") -async def connect_app( +@router.post("/auth/providers/{provider_name}/connect", include_in_schema=False) +async def connect_provider( provider_name: str, request: Request, background_tasks: BackgroundTasks, - auth: CredentialService = Depends(require_ui_auth()), + auth: CredentialService = Depends(require_ui_auth("/")), sessions: AuthSessionStore = Depends(get_auth_sessions), server_base_url: str = Depends(get_server_base_url), ) -> Response: - """Start a provider connection from the dashboard.""" + """Start a provider connection from the static dashboard.""" form = await request.form() connection_name = str(form.get("connection") or form.get("connection_name") or "default") force = str(form.get("force", "false")).lower() in {"1", "true", "on", "yes"} + return_path = _account_auth_next_url(form.get("return_url") or "/") definition = await auth.get_provider(provider_name) flow = definition.flow @@ -694,7 +161,7 @@ async def connect_app( ) session.payload["force"] = force session.payload["callback_url_override"] = build_callback_url(server_base_url) - session.payload["return_url"] = f"{server_base_url.rstrip('/')}/apps/{provider_name}" + session.payload["return_url"] = f"{server_base_url.rstrip('/')}{return_path}" session.payload["ui_session_required"] = True if not force: @@ -703,7 +170,7 @@ async def connect_app( if auth.has_usable_connection(existing): session.status_message = "Already connected" await sessions.save(session) - return _redirect(request, f"/apps/{provider_name}") + return _redirect(request, return_path) except Exception: pass @@ -727,44 +194,7 @@ async def connect_app( await sessions.save(session) return _redirect(request, str(auth_url)) await sessions.save(session) - return _redirect(request, f"/apps/{provider_name}") - - -@router.post("/apps/{provider_name}/configure") -async def configure_provider( - provider_name: str, - request: Request, - auth: CredentialService = Depends(require_ui_auth()), - sessions: AuthSessionStore = Depends(get_auth_sessions), - server_base_url: str = Depends(get_server_base_url), -) -> Response: - """Open the provider configuration flow for deployment-scoped credentials.""" - provider = await auth.get_provider(provider_name) - policy = _ui_policy(request, auth) - if not policy["show_provider_client_details"]: - return _admin_required_response( - "Admin access required", - "Provider configuration is available only to administrators.", - ) - if provider.auth_type != AuthType.OAUTH2: - return _redirect(request, f"/apps/{provider_name}") - - session = await sessions.create( - provider=provider_name, - identity=auth.identity, - principal_id=auth.principal_id, - connection_name="default", - flow_type=provider.flow.value, - ) - session.payload["provider_config_only"] = True - session.payload["existing_provider_client"] = (await auth.get_provider_client(provider_name)) is not None - session.payload["callback_url_override"] = build_callback_url(server_base_url) - session.payload["return_url"] = f"{server_base_url.rstrip('/')}/apps/{provider_name}" - session.payload["input_fields"] = [ - field.model_dump(mode="json", exclude_none=True) for field in await auth.get_required_inputs(session) - ] - await sessions.save(session) - return _redirect(request, build_auth_input_url(server_base_url, session.session_id)) + return _redirect(request, return_path) @router.post("/session", response_model=UiBootstrapResponse) @@ -783,7 +213,8 @@ async def logout_ui_session( ui_sessions: UiSessionStore = Depends(get_ui_sessions), ) -> Response: """Clear the dashboard browser session.""" - response = _redirect(request, "/") + form = await request.form() + response = _redirect(request, _account_auth_next_url(form.get("return_url") or "/")) cookie_value = request.cookies.get(UI_SESSION_COOKIE_NAME) if cookie_value: try: diff --git a/src/authsome/server/settings.py b/src/authsome/server/settings.py index 9d81a08c..353a03e0 100644 --- a/src/authsome/server/settings.py +++ b/src/authsome/server/settings.py @@ -41,7 +41,7 @@ class ServerSettings(BaseSettings): # ── Analytics ──────────────────────────────────────────────────────── analytics: bool = True # AUTHSOME_ANALYTICS=0 disables - posthog_api_key: str = "phc_sgDwzvbX3TjBoPiWREfyfGs9pwebebKjavHGQChh9BpH" + posthog_api_key: str = "YOUR_API_KEY_HERE" posthog_host: str = "https://us.i.posthog.com" # Industry-standard, non-prefixed opt-out flags. do_not_track: bool = Field(default=False, validation_alias=AliasChoices("DO_NOT_TRACK")) diff --git a/src/authsome/server/web_pages/pages.py b/src/authsome/server/web_pages/pages.py index e32c8688..575b6dd9 100644 --- a/src/authsome/server/web_pages/pages.py +++ b/src/authsome/server/web_pages/pages.py @@ -1,4 +1,8 @@ -"""HTML page generators for the Authsome server.""" +"""HTML page generators for the Authsome server. + +Design: Authsome Secure Console (see src/authsome/ui/DESIGN.md). +All pages follow the dark-first, developer-focused design system. +""" from __future__ import annotations @@ -7,26 +11,53 @@ from authsome.server.web_pages.web_theme import DARK_THEME_CSS, DEVICE_BRIDGE_STYLE +_BRAND = 'Authsome.' -def message_page(title: str, message: str) -> str: - """Generate a simple message page.""" + +def _page_shell(title: str, head_extra: str, body: str) -> str: return f""" - + {html.escape(title)} - + + {head_extra} -
-

{html.escape(title)}

-

{html.escape(message)}

-
+ {body} """ +def message_page(title: str, message: str) -> str: + """Generate a simple message/error page.""" + body = f""" +
+
+
{_BRAND}
+

{html.escape(title)}

+

+ {html.escape(message)} +

+
+
""" + return _page_shell(f"Authsome — {title}", "", body) + + def account_auth_page( *, next_url: str, @@ -35,168 +66,165 @@ def account_auth_page( error: str | None = None, ) -> str: """Generate the account sign-in/register page.""" - title = "Claim identity" if identity else "Open dashboard" + page_title = "Claim identity" if identity else "Authsome Dashboard" subtitle = ( - f"Sign in or create an account to claim {html.escape(identity)}." + f"Sign in to claim " + f"{html.escape(identity)} to your account." if identity - else "Sign in or create an account to open your Authsome dashboard." + else "Sign in or create an account to open your dashboard." ) - error_block = f'

{html.escape(error)}

' if error else "" - register_hidden = "hidden" if active_tab == "login" else "" - login_hidden = "hidden" if active_tab == "register" else "" - login_active = "is-active" if active_tab == "login" else "" - register_active = "is-active" if active_tab == "register" else "" - auth_tabs = f""" -
- - -
""" - register_panel = f""" -
-

Create account

+ + error_block = "" + if error: + error_block = f""" +
{html.escape(error)}
""" + + login_hidden = " hidden" if active_tab != "login" else "" + register_hidden = " hidden" if active_tab != "register" else "" + login_active = " tab-active" if active_tab == "login" else "" + register_active = " tab-active" if active_tab == "register" else "" + + body = f""" +
+ +
{_BRAND}
+ + +
+ +
+

{html.escape(page_title)}

+

+ {subtitle} +

+
+ + +
+ + +
+ + +
+

+ Welcome back. +

+ {error_block if active_tab == "login" else ""} +
+ +
+ + +
+
+ + +
+ +
+
+ + +
+

+ Get started in seconds — no billing information required. +

{error_block if active_tab == "register" else ""}
- - - - - +
+ + +
+
+ + +
+
-
""" - return f""" - - - - - Authsome - {title} - - - -
-
-
-

Authsome Account

-

{title}

-

{subtitle}

-
- {auth_tabs} -
-

Welcome back

- {error_block if active_tab == "login" else ""} -
- - - - - - -
-
- {register_panel} -
-
+ - -""" + """ + + return _page_shell(f"Authsome — {page_title}", "", body) def account_claim_auth_page(*, token: str, identity: str, error: str | None = None, active_tab: str = "login") -> str: @@ -210,25 +238,66 @@ def account_claim_auth_page(*, token: str, identity: str, error: str | None = No def account_claim_confirm_page(*, token: str, identity: str, email: str) -> str: - """Generate the account identity-claim confirmation page.""" - return f""" - - - - - Authsome - Claim identity - - - -
-

Claim identity

-

Confirm claiming {html.escape(identity)} to {html.escape(email)}.

-
- -
+ """Generate the identity-claim confirmation page.""" + body = f""" +
+
{_BRAND}
+ +
+
+

Claim identity

+

+ Review and confirm before linking. +

+
+ +
+ +
+
Identity
+
+ {html.escape(identity)} +
+
+ + +
+
Account
+
+ {html.escape(email)} +
+
+ +
+ +
+
+
+ +

+ This links your local key to your account permanently. +

- -""" + + """ + + return _page_shell("Authsome — Claim identity", "", body) def input_page( @@ -240,8 +309,8 @@ def input_page( warning_message: str | None = None, ) -> str: """Generate a dynamic input form for provider credentials.""" - required_rows = [] - optional_rows = [] + required_rows: list[str] = [] + optional_rows: list[str] = [] for field in fields: row = _field_row(field) if field.get("default") is None or field.get("name") in {"client_id", "client_secret"}: @@ -249,110 +318,144 @@ def input_page( else: optional_rows.append(row) - docs = ( - f'

Provider documentation

' - if docs_url - else "" - ) + docs = "" + if docs_url: + docs = f""" + + Provider documentation ↗ + """ - callback_hint = "" + callback_block = "" if callback_url: - callback_hint = f""" -
-
""" - warning = "" + warning_block = "" if warning_message: - warning = f""" + warning_block = f"""
- Warning - {html.escape(warning_message)} +
Warning
+ {html.escape(warning_message)} +
""" + + optional_block = "" + if optional_rows: + optional_block = f""" +
+ Advanced options + {"".join(optional_rows)} +
""" + + body = f""" +
+
{_BRAND}
+ +
+
+

{html.escape(display_name)}

+

+ Enter the required credentials to continue. +

- """ - script = "" - if callback_url: - script = """ - """ - return f""" - - - - - Authsome - {html.escape(display_name)} - - - -
-

{html.escape(display_name)}

- {docs} - {callback_hint} - {warning} -
- {"".join(required_rows)} - {optional} - -
-
{script} - -""" - def _field_row(field: dict[str, Any]) -> str: name = html.escape(str(field["name"])) @@ -361,11 +464,14 @@ def _field_row(field: dict[str, Any]) -> str: value = html.escape(str(field.get("default") or "")) required = " required" if field.get("default") is None else "" pattern = f' pattern="{html.escape(str(field["pattern"]))}"' if field.get("pattern") else "" - hint = f"{html.escape(str(field['pattern_hint']))}" if field.get("pattern_hint") else "" + hint = "" + if field.get("pattern_hint"): + hint = f"{html.escape(str(field['pattern_hint']))}" return ( f'
' f'' - f'' + f'' f"{hint}
" ) @@ -376,85 +482,54 @@ def device_code_page( verification_uri: str, verification_uri_complete: str | None, ) -> str: - """Generate a device code verification page with a premium theme.""" + """Generate a device code verification page.""" link = verification_uri_complete or verification_uri return f""" - + - Authsome - Device Login + Authsome — Device login {DEVICE_BRIDGE_STYLE} -
Authsome
+
Authsome.

{html.escape(display_name)}

-

Enter the following code on your device to complete the login.

- +

Enter this code on the login page to complete authentication.

+
- +
- - Open Login Page - -

- After completing the login on your device, return to your terminal. -

+ + + Open login page ↗ + + +

After completing login in your browser, return to your terminal.

diff --git a/src/authsome/server/web_pages/web_theme.py b/src/authsome/server/web_pages/web_theme.py index b0567cfd..6cbea41a 100644 --- a/src/authsome/server/web_pages/web_theme.py +++ b/src/authsome/server/web_pages/web_theme.py @@ -1,203 +1,291 @@ -"""Web UI themes and styles for Authsome local server.""" +"""Web UI themes and styles for Authsome local server. + +Design system: Authsome Secure Console (see src/authsome/ui/DESIGN.md). +Palette: Deep Emerald (#10B981) + Obsidian (#09090B), dark-first. +Fonts: Hanken Grotesk (UI) + JetBrains Mono (data/code). +""" from __future__ import annotations -DARK_THEME_CSS = """ -:root { - color-scheme: dark; - --bg: #000000; - --panel: #0a0a0a; - --text: #ededed; - --muted: #a1a1aa; - --line: #27272a; - --accent: #83ca16; - --focus: var(--accent); - --primary: #ededed; - --primary-text: #000000; -} -* { box-sizing: border-box; } -body { - margin: 0; - min-height: 100vh; - background: var(--bg); - color: var(--text); - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - -webkit-font-smoothing: antialiased; - display: flex; - align-items: center; - justify-content: center; - padding: 20px; -} -main { - width: 100%; - max-width: 440px; - background: var(--panel); - border: 1px solid var(--line); - border-radius: 8px; - padding: 32px; -} -h1 { - margin: 0 0 4px; - font-size: 20px; - font-weight: 500; - letter-spacing: -0.02em; -} -p { - margin: 0 0 24px; - color: var(--muted); - font-size: 14px; -} -a { - color: var(--text); - text-decoration: none; -} -a:hover { color: var(--accent); } -form { margin-top: 16px; } -.field-group { margin-bottom: 16px; } -label { - display: block; - font-size: 13px; - font-weight: 500; - margin-bottom: 6px; - color: var(--text); -} -input { - width: 100%; - background: var(--bg); - border: 1px solid var(--line); - border-radius: 6px; - color: var(--text); - font-family: inherit; - font-size: 14px; - padding: 10px 12px; - transition: border-color 0.15s; -} -input:focus { - outline: none; - border-color: var(--focus); -} -small { - display: block; - margin-top: 6px; - color: var(--muted); - font-size: 12px; -} -button { - width: 100%; - background: var(--primary); - color: var(--primary-text); - border: none; - border-radius: 6px; - padding: 10px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - margin-top: 12px; - transition: opacity 0.15s; -} -button:hover { opacity: 0.9; } -details { - margin: 24px 0; -} -summary { - font-size: 13px; - font-weight: 500; - color: var(--muted); - cursor: pointer; - user-select: none; - transition: color 0.15s; -} -summary:hover { - color: var(--accent); -} -details[open] summary { margin-bottom: 16px; } +_FONT_IMPORT = """ + @import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap'); """ -DEVICE_BRIDGE_STYLE = """ - -""" + box-shadow: 0 0 20px -8px var(--accent); +}} +a.verify:hover {{ opacity: 0.88; }} +.note {{ font-size: 13px; color: var(--muted); text-align: center; line-height: 1.5; }} +""" diff --git a/src/authsome/ui/static/app.js b/src/authsome/ui/static/app.js deleted file mode 100644 index f89e396d..00000000 --- a/src/authsome/ui/static/app.js +++ /dev/null @@ -1,124 +0,0 @@ -(() => { - const ready = (fn) => - document.readyState === "loading" - ? document.addEventListener("DOMContentLoaded", fn) - : fn(); - - function initSearchAndFilter() { - const grid = document.getElementById("connectionList") || document.getElementById("appGrid"); - const search = document.getElementById("appSearch"); - const empty = document.getElementById("appEmpty"); - if (!grid) return; - - let activeFilter = "all"; - const cards = Array.from(grid.querySelectorAll(".app-card, .connection-row")); - - const applyFilters = () => { - const q = (search?.value || "").trim().toLowerCase(); - let visible = 0; - cards.forEach((card) => { - const matchesQuery = !q || card.dataset.name.includes(q); - const status = card.dataset.status; - const matchesFilter = - !document.querySelector(".filter-pill") || - activeFilter === "all" || - (activeFilter === "connected" && status !== "available") || - (activeFilter === "available" && status === "available"); - const show = matchesQuery && matchesFilter; - card.classList.toggle("hidden", !show); - if (show) visible += 1; - }); - if (empty) empty.classList.toggle("hidden", visible !== 0); - }; - - document.querySelectorAll(".filter-pill").forEach((pill) => { - pill.addEventListener("click", () => { - document - .querySelectorAll(".filter-pill") - .forEach((p) => p.classList.remove("active")); - pill.classList.add("active"); - activeFilter = pill.dataset.filter || "all"; - applyFilters(); - }); - }); - - if (search) search.addEventListener("input", applyFilters); - } - - function initSecretToggles() { - document.querySelectorAll("[data-toggle-secret]").forEach((btn) => { - btn.addEventListener("click", () => { - const row = btn.closest(".field-row"); - const target = row?.querySelector("[data-secret]"); - if (!target) return; - const real = target.dataset.secretValue; - if (!real) return; - const isMasked = target.classList.contains("mask"); - if (isMasked) { - target.dataset.maskedDisplay = target.textContent; - target.textContent = real; - target.classList.remove("mask"); - } else { - target.textContent = target.dataset.maskedDisplay || "••••••••••••••••"; - target.classList.add("mask"); - } - }); - }); - } - - function initCopyButtons() { - document.querySelectorAll("[data-copy]").forEach((btn) => { - btn.addEventListener("click", async () => { - const value = btn.dataset.copy; - if (!value) return; - try { - await navigator.clipboard.writeText(value); - const original = btn.title; - btn.title = "Copied"; - btn.classList.add("active"); - setTimeout(() => { - btn.title = original; - btn.classList.remove("active"); - }, 900); - } catch { - /* clipboard write failed; nothing fatal to do here */ - } - }); - }); - } - - function initLoginModal() { - const modal = document.getElementById("loginModal"); - const form = document.getElementById("loginModalForm"); - const hiddenConnection = document.getElementById("loginConnectionName"); - const input = document.getElementById("connectionNameInput"); - if (!modal || !form || !hiddenConnection || !input) return; - - document.querySelectorAll("[data-open-login-modal]").forEach((btn) => { - btn.addEventListener("click", () => { - const provider = btn.dataset.provider; - if (!provider) return; - form.action = `/apps/${provider}/connect`; - hiddenConnection.value = ""; - input.value = ""; - modal.showModal(); - input.focus(); - }); - }); - - document.querySelectorAll("[data-close-login-modal]").forEach((btn) => { - btn.addEventListener("click", () => modal.close()); - }); - - form.addEventListener("submit", () => { - hiddenConnection.value = input.value.trim(); - }); - } - - ready(() => { - initSearchAndFilter(); - initSecretToggles(); - initCopyButtons(); - initLoginModal(); - }); -})(); diff --git a/src/authsome/ui/static/style.css b/src/authsome/ui/static/style.css deleted file mode 100644 index 8704117f..00000000 --- a/src/authsome/ui/static/style.css +++ /dev/null @@ -1,1130 +0,0 @@ -:root { - --bg-body: #f5f6f8; - --bg-sidebar: #ffffff; - --bg-surface: #ffffff; - --bg-subtle: #eef2f5; - --bg-muted: #e6ebf0; - --border: #d7dde5; - --border-strong: #b9c3cf; - --accent: #1f6f5b; - --accent-dark: #155041; - --accent-soft: #e5f1ed; - --blue: #2f5d88; - --blue-soft: #e7eef6; - --warn: #9a5b13; - --warn-soft: #fff3dd; - --danger: #a33a3a; - --danger-soft: #f9e4e4; - --text: #16202a; - --text-secondary: #3d4a57; - --text-muted: #6d7a86; - --shadow: 0 1px 2px rgba(22, 32, 42, 0.06); - --radius-sm: 5px; - --radius-md: 6px; - --radius-lg: 8px; - --font-sans: Aptos, "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif; - --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; -} - -* { - box-sizing: border-box; -} - -html, -body { - margin: 0; - padding: 0; - min-height: 100vh; - background: var(--bg-body); - color: var(--text); - font-family: var(--font-sans); - font-size: 14px; - line-height: 1.5; -} - -body { - display: grid; - grid-template-columns: 232px minmax(0, 1fr); -} - -a { - color: inherit; - text-decoration: none; -} - -button, -input { - font: inherit; -} - -.sidebar { - position: sticky; - top: 0; - height: 100vh; - display: flex; - flex-direction: column; - gap: 6px; - padding: 18px 14px 16px; - background: var(--bg-sidebar); - border-right: 1px solid var(--border); -} - -.brand { - padding: 5px 10px 16px; - font-size: 15px; - font-weight: 700; - letter-spacing: 0; -} - -.brand::after { - content: "."; - color: var(--accent); -} - -.nav-primary, -.nav-secondary { - display: flex; - flex-direction: column; - gap: 2px; -} - -.nav-spacer { - flex: 1; -} - -.nav-item { - display: flex; - align-items: center; - gap: 10px; - min-height: 36px; - padding: 8px 10px; - border: 1px solid transparent; - border-radius: var(--radius-md); - color: var(--text-secondary); - font-size: 13px; - transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease; -} - -.nav-item:hover { - background: var(--bg-subtle); - color: var(--text); -} - -.nav-item.active { - background: var(--accent-soft); - border-color: #b8d9cf; - color: var(--accent-dark); - font-weight: 600; -} - -.nav-item.disabled { - opacity: 0.45; - pointer-events: none; -} - -.nav-item svg { - width: 16px; - height: 16px; - flex-shrink: 0; -} - -.brand-foot { - padding: 8px 10px 2px; - color: var(--text-muted); - font-size: 11px; -} - -.main { - min-width: 0; - display: flex; - flex-direction: column; -} - -.topbar { - min-height: 57px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 12px 30px; - background: rgba(255, 255, 255, 0.88); - border-bottom: 1px solid var(--border); - backdrop-filter: blur(8px); -} - -.topbar-meta, -.topbar-actions, -.page-actions { - display: flex; - align-items: center; - gap: 8px; -} - -.topbar-actions { - justify-content: flex-end; -} - -.topbar-actions form { - margin: 0; -} - -.meta { - color: var(--text-muted); - font-size: 12.5px; -} - -.page { - width: 100%; - max-width: 1220px; - padding: 26px 30px 48px; -} - -.page-header { - display: flex; - align-items: flex-end; - justify-content: space-between; - gap: 16px; - margin-bottom: 22px; -} - -.eyebrow { - margin-bottom: 4px; - color: var(--text-muted); - font-size: 11px; - font-weight: 700; - letter-spacing: 0; - text-transform: uppercase; -} - -h1, -h2 { - margin: 0; - letter-spacing: 0; -} - -h1 { - font-size: 24px; - line-height: 1.2; - font-weight: 700; -} - -h2 { - font-size: 14px; - font-weight: 700; -} - -.subtitle { - margin: 5px 0 0; - color: var(--text-muted); - font-size: 13px; -} - -.muted, -.subtle { - color: var(--text-muted); -} - -.link { - color: var(--accent-dark); - font-size: 13px; - font-weight: 600; -} - -.link:hover { - text-decoration: underline; -} - -.btn, -.icon-btn { - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: var(--bg-surface); - color: var(--text); - cursor: pointer; - transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease; -} - -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - min-height: 34px; - padding: 7px 12px; - font-size: 13px; - font-weight: 600; - white-space: nowrap; -} - -.btn:hover, -.icon-btn:hover { - background: var(--bg-subtle); - border-color: var(--border-strong); -} - -.btn svg { - width: 14px; - height: 14px; - flex-shrink: 0; -} - -.btn[disabled] { - cursor: not-allowed; - opacity: 0.55; -} - -.btn-sm { - min-height: 30px; - padding: 5px 10px; - font-size: 12px; -} - -.btn-primary { - background: var(--accent); - border-color: var(--accent); - color: #ffffff; -} - -.btn-primary:hover { - background: var(--accent-dark); - border-color: var(--accent-dark); -} - -.btn-secondary { - background: var(--bg-surface); - color: var(--text-secondary); -} - -.btn-light { - background: var(--blue-soft); - border-color: #c8d6e5; - color: var(--blue); -} - -.btn-warn-ghost { - background: var(--warn-soft); - border-color: #eed09f; - color: var(--warn); -} - -.btn-danger-ghost { - background: var(--danger-soft); - border-color: #efc3c3; - color: var(--danger); -} - -.btn-block { - width: 100%; -} - -.icon-btn { - width: 30px; - height: 30px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - padding: 0; -} - -.icon-btn svg { - width: 15px; - height: 15px; -} - -.pill { - display: inline-flex; - align-items: center; - gap: 4px; - max-width: 100%; - padding: 3px 8px; - border: 1px solid var(--border); - border-radius: 999px; - background: var(--bg-subtle); - color: var(--text-secondary); - font-size: 11.5px; - font-weight: 600; - line-height: 1.35; - white-space: nowrap; -} - -.pill-accent { - background: var(--accent-soft); - border-color: #b8d9cf; - color: var(--accent-dark); -} - -.pill-neutral { - background: var(--bg-subtle); - border-color: var(--border); - color: var(--text-secondary); -} - -.pill-warn { - background: var(--warn-soft); - border-color: #efd4aa; - color: var(--warn); -} - -.stat-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 14px; - margin-bottom: 26px; -} - -.stat-card, -.card, -.panel, -.notice-band, -.provider-card, -.app-card, -.empty, -.docs-card, -.connection-row { - background: var(--bg-surface); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow); -} - -.stat-card { - min-height: 118px; - display: flex; - flex-direction: column; - gap: 6px; - padding: 16px 18px; -} - -.stat-label, -.card-title { - color: var(--text-muted); - font-size: 11px; - font-weight: 700; - letter-spacing: 0; - text-transform: uppercase; -} - -.stat-value { - font-size: 31px; - line-height: 1.1; - font-weight: 700; - letter-spacing: 0; -} - -.stat-value-sm { - font-size: 19px; - line-height: 1.25; -} - -.stat-foot { - margin-top: auto; - color: var(--text-muted); - font-size: 12px; -} - -.section { - margin-bottom: 26px; -} - -.section-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-bottom: 12px; -} - -.provider-grid, -.app-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 12px; -} - -.provider-card { - min-height: 166px; - display: flex; - flex-direction: column; - gap: 9px; - padding: 15px; - transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease; -} - -.provider-card:hover, -.app-card:hover, -.connection-row:hover, -.docs-card:hover { - border-color: var(--border-strong); - box-shadow: 0 2px 7px rgba(22, 32, 42, 0.08); -} - -.provider-card-top { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} - -.logo { - width: 32px; - height: 32px; - border-radius: var(--radius-md); - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - background: var(--blue-soft); - color: var(--blue); - font-size: 13px; - font-weight: 700; -} - -.logo-lg { - width: 44px; - height: 44px; - font-size: 16px; -} - -.provider-card-name { - color: var(--text); - font-size: 14px; - font-weight: 700; -} - -.provider-card-desc { - min-height: 18px; - color: var(--text-muted); - font-size: 12.5px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.provider-card-foot { - margin-top: auto; - padding-top: 10px; - border-top: 1px solid var(--border); - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - color: var(--text-muted); - font-size: 12px; -} - -.icon-gear { - display: inline-flex; - color: var(--text-muted); -} - -.notice-band { - display: flex; - align-items: center; - justify-content: space-between; - gap: 18px; - padding: 18px 20px; -} - -.notice-title { - margin-bottom: 3px; - font-size: 14px; - font-weight: 700; -} - -.notice-body { - max-width: 680px; - margin: 0; - color: var(--text-muted); - font-size: 13px; -} - -.toolbar { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 18px; -} - -.search { - min-height: 38px; - display: flex; - align-items: center; - gap: 8px; - flex: 1; - padding: 8px 12px; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: var(--bg-surface); - color: var(--text-muted); - box-shadow: var(--shadow); -} - -.search input { - width: 100%; - min-width: 0; - border: 0; - outline: 0; - background: transparent; - color: var(--text); - font-size: 13px; -} - -.filter-pills { - display: flex; - gap: 4px; - padding: 3px; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: var(--bg-surface); -} - -.filter-pill { - display: inline-flex; - align-items: center; - gap: 6px; - min-height: 29px; - padding: 5px 11px; - border: 0; - border-radius: var(--radius-md); - background: transparent; - color: var(--text-secondary); - cursor: pointer; - font-size: 12.5px; -} - -.filter-pill.active { - background: var(--bg-subtle); - color: var(--text); -} - -.filter-pill .count { - color: var(--accent-dark); - font-size: 12px; - font-weight: 700; -} - -.app-card { - position: relative; - min-height: 76px; - display: flex; - align-items: center; - gap: 12px; - padding: 13px 14px; -} - -.app-card.is-connected { - border-color: #b8d9cf; -} - -.app-card-hit { - position: absolute; - inset: 0; - z-index: 1; - border-radius: inherit; -} - -.app-card-text { - min-width: 0; - flex: 1; -} - -.app-card-name { - overflow: hidden; - color: var(--text); - font-size: 13.5px; - font-weight: 700; - text-overflow: ellipsis; - white-space: nowrap; -} - -.app-card-sub { - color: var(--text-muted); - font-size: 12px; -} - -.inline-action { - position: relative; - z-index: 2; - margin: 0; -} - -.as-button { - cursor: pointer; -} - -.empty { - padding: 38px 24px; - color: var(--text-muted); - text-align: center; -} - -.empty-grid { - grid-column: 1 / -1; -} - -.empty-title { - margin-bottom: 4px; - color: var(--text); - font-size: 14px; - font-weight: 700; -} - -.empty-body { - margin-bottom: 14px; - font-size: 13px; -} - -.empty .btn { - margin-top: 4px; -} - -.hidden { - display: none !important; -} - -.breadcrumb { - margin-bottom: 12px; - color: var(--text-muted); - font-size: 12.5px; -} - -.breadcrumb a { - color: var(--text-secondary); -} - -.breadcrumb a:hover { - color: var(--accent-dark); -} - -.breadcrumb .sep { - margin: 0 6px; - opacity: 0.6; -} - -.detail-header { - display: flex; - align-items: center; - gap: 14px; - margin-bottom: 18px; -} - -.detail-title { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 10px; - font-size: 23px; -} - -.detail-meta { - margin-top: 3px; - color: var(--text-muted); - font-size: 12.5px; -} - -.detail-grid { - display: grid; - grid-template-columns: minmax(0, 1fr) 260px; - gap: 18px; - align-items: start; -} - -.detail-main, -.detail-side, -.connection-list, -.action-stack { - display: flex; - flex-direction: column; - gap: 12px; -} - -.detail-side { - position: sticky; - top: 72px; -} - -.docs-card { - display: flex; - align-items: center; - gap: 10px; - padding: 12px 14px; -} - -.docs-icon { - width: 30px; - height: 30px; - border-radius: var(--radius-md); - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - background: var(--accent-soft); - color: var(--accent-dark); -} - -.docs-icon svg { - width: 16px; - height: 16px; -} - -.docs-title { - display: block; - font-size: 13px; - font-weight: 700; -} - -.docs-subtitle { - display: block; - margin-top: 1px; - color: var(--text-muted); - font-size: 12px; -} - -.card, -.panel { - padding: 16px 18px; -} - -.card-title { - margin-bottom: 10px; -} - -.card-body { - margin: 0; - color: var(--text-secondary); - font-size: 13px; -} - -.card-warn { - background: var(--warn-soft); - border-color: #efd4aa; -} - -.field { - margin-bottom: 12px; -} - -.field:last-child { - margin-bottom: 0; -} - -.field-label { - margin-bottom: 4px; - color: var(--text-muted); - font-size: 12px; - font-weight: 600; -} - -.field-row { - min-height: 34px; - display: flex; - align-items: center; - gap: 6px; - padding: 7px 10px; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: #fbfcfd; -} - -.field-row .mono { - min-width: 0; - flex: 1; - overflow: hidden; - color: var(--text-secondary); - text-overflow: ellipsis; - white-space: nowrap; -} - -.mono { - font-family: var(--font-mono); - font-size: 12.5px; -} - -.mask { - letter-spacing: 0; -} - -.scope-row { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.scope { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 4px 8px 4px 10px; - border: 1px solid #b8d9cf; - border-radius: 999px; - background: var(--accent-soft); - color: var(--accent-dark); - font-family: var(--font-mono); - font-size: 12px; -} - -.scope-x { - padding: 0 2px; - border: 0; - background: transparent; - color: var(--accent-dark); - cursor: pointer; - font-size: 14px; - line-height: 1; -} - -.scope.scope-add { - border-color: var(--border); - border-style: dashed; - background: var(--bg-surface); - color: var(--text-muted); - cursor: pointer; -} - -.kv { - display: grid; - grid-template-columns: max-content minmax(0, 1fr); - column-gap: 12px; - row-gap: 8px; - margin: 0; - font-size: 12.5px; -} - -.kv dt { - color: var(--text-muted); -} - -.kv dd { - min-width: 0; - margin: 0; - color: var(--text); -} - -.action-stack form { - margin: 0; -} - -.connection-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 12px 14px; -} - -.connection-row-main { - min-width: 0; - display: flex; - flex-direction: column; - gap: 2px; -} - -.connection-row-name { - color: var(--text); - font-size: 13.5px; - font-weight: 700; -} - -.connection-row-meta { - overflow: hidden; - color: var(--text-muted); - font-size: 12px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.connection-row-readonly { - padding: 10px 12px; - box-shadow: none; -} - -.connection-group + .connection-group { - margin-top: 14px; -} - -.identity-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 12px 0; - border-bottom: 1px solid var(--border); -} - -.identity-row:first-child { - padding-top: 0; -} - -.identity-row:last-child { - padding-bottom: 0; - border-bottom: 0; -} - -.identity-name { - color: var(--text); - font-weight: 700; -} - -.identity-meta { - color: var(--text-muted); - font-size: 12px; -} - -.table-wrap { - width: 100%; - overflow-x: auto; -} - -.data-table { - width: 100%; - min-width: 760px; - border-collapse: collapse; - font-size: 13px; -} - -.data-table th, -.data-table td { - padding: 11px 12px; - border-bottom: 1px solid var(--border); - text-align: left; - vertical-align: top; -} - -.data-table th { - color: var(--text-muted); - font-size: 11px; - font-weight: 700; - letter-spacing: 0; - text-transform: uppercase; -} - -.data-table tbody tr:last-child td { - border-bottom: 0; -} - -.table-primary { - color: var(--text); - font-weight: 700; -} - -.table-secondary { - margin-top: 2px; - color: var(--text-muted); - font-size: 11.5px; -} - -.audit-table td:nth-child(1) { - white-space: nowrap; -} - -.audit-metadata-row td { - padding-top: 0; - background: #fbfcfd; -} - -.metadata-list { - display: flex; - flex-wrap: wrap; - gap: 8px 14px; - margin: 0; - color: var(--text-muted); - font-size: 12px; -} - -.metadata-list div { - display: inline-flex; - gap: 5px; -} - -.metadata-list dt { - color: var(--text-muted); - font-weight: 700; -} - -.metadata-list dd { - margin: 0; - color: var(--text-secondary); -} - -.login-modal { - width: min(420px, calc(100vw - 32px)); - padding: 0; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: var(--bg-surface); - color: var(--text); - box-shadow: 0 18px 44px rgba(22, 32, 42, 0.22); -} - -.login-modal::backdrop { - background: rgba(20, 29, 38, 0.44); -} - -.login-modal form { - display: flex; - flex-direction: column; - gap: 14px; - margin: 0; - padding: 18px; -} - -.login-modal-input { - width: 100%; - padding: 9px 10px; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: #fbfcfd; - color: var(--text); - font: inherit; -} - -@media (max-width: 980px) { - body { - grid-template-columns: 1fr; - } - - .sidebar { - position: static; - height: auto; - border-right: 0; - border-bottom: 1px solid var(--border); - } - - .nav-primary, - .nav-secondary { - flex-flow: row wrap; - } - - .nav-spacer, - .brand-foot { - display: none; - } - - .topbar, - .page-header, - .notice-band { - align-items: flex-start; - flex-direction: column; - } - - .topbar-actions, - .page-actions { - flex-wrap: wrap; - } - - .page { - padding: 22px 18px 36px; - } - - .stat-grid, - .provider-grid, - .app-grid, - .detail-grid { - grid-template-columns: 1fr; - } - - .detail-side { - position: static; - } -} diff --git a/src/authsome/ui/templates/_app_detail_shell.html b/src/authsome/ui/templates/_app_detail_shell.html deleted file mode 100644 index 94f113dc..00000000 --- a/src/authsome/ui/templates/_app_detail_shell.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends "_layout.html" %} -{% block title %}{{ provider.display_name }}{% endblock %} -{% block content %} - - -
- -
-

- {{ provider.display_name }} - {% if connection %} - ● Connected - {% else %} - Available - {% endif %} -

-
{% block detail_meta %}{% endblock %} · {{ api_url }}
-
-
- -
-
- {% block credentials %}{% endblock %} -
- - -
-{% endblock %} diff --git a/src/authsome/ui/templates/_layout.html b/src/authsome/ui/templates/_layout.html deleted file mode 100644 index eb0bb9cc..00000000 --- a/src/authsome/ui/templates/_layout.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - {% block title %}Authsome{% endblock %} · Authsome - - - - - -
-
- {% if show_identity %} -
- {% if role_label %}{{ role_label }}{% endif %} - Signed in as {{ ui_email or ui_identity }} -
- {% endif %} -
- {% if show_identity %} -
- -
- {% endif %} - - - GitHub - - - - PyPI - -
-
- -
- {% block content %}{% endblock %} -
-
- - - - - diff --git a/src/authsome/ui/templates/app_detail_apikey.html b/src/authsome/ui/templates/app_detail_apikey.html deleted file mode 100644 index de701484..00000000 --- a/src/authsome/ui/templates/app_detail_apikey.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "_app_detail_shell.html" %} -{% block detail_meta %}API Key{% endblock %} - -{% block credentials %} -
-
API Credentials
-
-
API Key
-
- {% if api_key %}••••••••••••••••{% else %}—{% endif %} - {% if api_key %} - - - {% endif %} -
-
-
- -
-
Configuration
-
-
Base URL
-
{{ base_url or '—' }}
-
-
-{% endblock %} - -{% block side_rows %} -
Auth type
API Key
-{% endblock %} - -{% block actions %} - -
- -
-{% endblock %} diff --git a/src/authsome/ui/templates/app_detail_disconnected.html b/src/authsome/ui/templates/app_detail_disconnected.html deleted file mode 100644 index 1399dba8..00000000 --- a/src/authsome/ui/templates/app_detail_disconnected.html +++ /dev/null @@ -1,79 +0,0 @@ -{% extends "_app_detail_shell.html" %} -{% block detail_meta %}{{ auth_type_label }}{% endblock %} - -{% block credentials %} -
-
Setup
-

- {{ provider.display_name }} is not connected yet. Review the provider configuration and use Connect when you're ready. -

-
- -{% if provider.auth_type.value == "oauth2" %} -
-
{{ provider_management_label }}
- {% if show_provider_client_details %} -
-
Client ID
-
{{ client_id or 'Required during setup' }}
-
-
-
Client Secret
-
{% if has_client_secret %}••••••••••••••••{% else %}Required during setup{% endif %}
-
- {% else %} -

- Authsome manages the OAuth application for {{ provider.display_name }}. Start the connection flow and Authsome will use the configured client automatically. -

- {% endif %} -
-
Redirect URI
-
- {{ redirect_uri }} - -
-
-
- -
-
Endpoints
-
-
Authorization URL
-
{{ auth_url or '—' }}
-
-
-
Token URL
-
{{ token_url or '—' }}
-
-
-{% else %} -
-
API Credentials
-
-
API Key
-
Required during setup
-
-
- -
-
Configuration
-
-
Base URL
-
{{ base_url or '—' }}
-
-
-{% endif %} -{% endblock %} - -{% block side_rows %} -
Auth type
{{ auth_type_label }}
-{% endblock %} - -{% block actions %} -
- - -
-{% endblock %} diff --git a/src/authsome/ui/templates/app_detail_managed.html b/src/authsome/ui/templates/app_detail_managed.html deleted file mode 100644 index 6c070ed6..00000000 --- a/src/authsome/ui/templates/app_detail_managed.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "_layout.html" %} -{% block title %}{{ provider.display_name }}{% endblock %} -{% block content %} - - -
- -
-

{{ provider.display_name }}

-
Managed by Authsome
-
-
- -
-
Provider configuration
-

- Authsome manages the OAuth application and related connections for {{ provider.display_name }}. - Provider configuration is available only to administrators. -

-
-{% endblock %} diff --git a/src/authsome/ui/templates/app_detail_oauth.html b/src/authsome/ui/templates/app_detail_oauth.html deleted file mode 100644 index 6007f851..00000000 --- a/src/authsome/ui/templates/app_detail_oauth.html +++ /dev/null @@ -1,70 +0,0 @@ -{% extends "_app_detail_shell.html" %} -{% block detail_meta %}OAuth 2.0{% endblock %} - -{% block credentials %} -
-
Scopes
-
- {% for scope in scopes %} - {{ scope }} - {% endfor %} - -
-
- -
-
Tokens
-
-
Access Token
-
- {% if access_token %}••••••••••••••••{% else %}—{% endif %} - {% if access_token %} - - - {% endif %} -
-
- {% if refresh_token %} -
-
Refresh Token
-
- •••••••••••••••• - - -
-
- {% endif %} - {% if expires_label %} -
-
Expires
-
{{ expires_label }}
-
- {% endif %} -
-{% endblock %} - -{% block side_rows %} -{% if connection.account and connection.account.label %} -
Account
{{ connection.account.label }}
-{% endif %} -{% endblock %} - -{% block actions %} - -
- - - -
-
- -
-{% endblock %} diff --git a/src/authsome/ui/templates/app_provider.html b/src/authsome/ui/templates/app_provider.html deleted file mode 100644 index 1e901983..00000000 --- a/src/authsome/ui/templates/app_provider.html +++ /dev/null @@ -1,128 +0,0 @@ -{% extends "_layout.html" %} -{% block title %}{{ provider.display_name }}{% endblock %} -{% block content %} - - -
- -
-

{{ provider.display_name }}

-
{{ auth_type_label }} · {{ api_url }}
-
-
- -
-
- {% if provider.auth_type.value == "oauth2" %} -
-
{{ provider_management_label }}
- {% if show_provider_client_details %} -
-
Client ID
-
{{ client_id or 'Required during setup' }}
-
-
-
Client Secret
-
{% if has_client_secret %}••••••••••••••••{% else %}Required during setup{% endif %}
-
-
-
Redirect URI
-
- {{ redirect_uri }} - -
-
- {% else %} -

- This deployment manages the OAuth application for {{ provider.display_name }}. -

- {% endif %} -
- -
-
Replace provider credentials
-

Changing these credentials revokes existing connections for this provider. Continue only if you intend to reconnect them.

-
- -
-
Endpoints
-
-
Authorization URL
-
{{ auth_url or '—' }}
-
-
-
Token URL
-
{{ token_url or '—' }}
-
-
- {% endif %} - -
-
Existing connections
- {% if grouped_connections %} - {% for group in grouped_connections %} -
-
{{ group.vault_label }}
-
- {% for item in group.connections %} - -
-
{{ item.connection_name }}
-
{{ item.identity }}
-
- {{ item.status }} -
- {% endfor %} -
-
- {% endfor %} - {% else %} -

No connections for this provider yet.

- {% endif %} -
-
- - -
- - -{% endblock %} diff --git a/src/authsome/ui/templates/applications.html b/src/authsome/ui/templates/applications.html deleted file mode 100644 index 4565d363..00000000 --- a/src/authsome/ui/templates/applications.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends "_layout.html" %} -{% block title %}Applications{% endblock %} -{% block content %} - - -
- -
- -
- {% for p in providers %} -
- - -
-
{{ p.display_name }}
-
{{ p.auth_type_label }}
-
- -
- {% endfor %} -
- - - - -{% endblock %} diff --git a/src/authsome/ui/templates/audit.html b/src/authsome/ui/templates/audit.html deleted file mode 100644 index d785d33c..00000000 --- a/src/authsome/ui/templates/audit.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends "_layout.html" %} -{% block title %}Audit{% endblock %} -{% block content %} - - -{% if audit_events %} -
-
- - - - - - - - - - - - - {% for event in audit_events %} - - - - - - - - - {% if event.metadata %} - - - - - {% endif %} - {% endfor %} - -
TimeEventActorTargetStatusSource
{{ event.time }} -
{{ event.event }}
-
{{ event.event_id }}
-
{{ event.actor }}{{ event.target }} - {% if event.status != "-" %} - {{ event.status }} - {% else %} - - - {% endif %} - {{ event.source }}
-
-
-{% else %} -
-
No audit events yet
-
Events will appear here after authentication, provider, or proxy activity is recorded.
-
-{% endif %} -{% endblock %} diff --git a/src/authsome/ui/templates/connections.html b/src/authsome/ui/templates/connections.html deleted file mode 100644 index f08cb16c..00000000 --- a/src/authsome/ui/templates/connections.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends "_layout.html" %} -{% block title %}Connections{% endblock %} -{% block content %} - - -
- -
- -{% if connection_rows %} - -{% else %} -
-
No connections yet
-
Go to Applications to configure a provider and start a new login.
- Add new connection -
-{% endif %} - - -{% endblock %} diff --git a/src/authsome/ui/templates/identity.html b/src/authsome/ui/templates/identity.html deleted file mode 100644 index 5d953dc2..00000000 --- a/src/authsome/ui/templates/identity.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "_layout.html" %} -{% block title %}Identity{% endblock %} -{% block content %} - - -
-{% for identity in identities %} -
-
-
{{ identity.handle }}
-
Accepted principal claim
-
- Active -
-{% endfor %} -
-{% endblock %} diff --git a/src/authsome/ui/templates/overview.html b/src/authsome/ui/templates/overview.html deleted file mode 100644 index 9e49f410..00000000 --- a/src/authsome/ui/templates/overview.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends "_layout.html" %} -{% block title %}Overview{% endblock %} -{% block content %} - - -
-
-
Connected Apps
-
{{ stats.connected }}
-
{{ stats.available }} more available
-
- -
-
Next Expiry
-
{{ last_activity }}
-
Across connected providers
-
- -
-
Auth Types
-
{{ stats.oauth }} / {{ stats.api_key }}
-
OAuth 2.0 / API Key
-
-
- -
-
-

Connected Providers

- Manage Connections -
- - {% if connected_providers %} - - {% else %} -
-
No connections yet
-
Connect your first provider to get started.
- Browse providers -
- {% endif %} -
- -{% if show_admin_sections %} -
-
-
Administrative controls
-

Provider configuration and audit events are restricted to administrators.

-
- Review audit -
-{% endif %} -{% endblock %} diff --git a/src/authsome/ui/web/.gitkeep b/src/authsome/ui/web/.gitkeep new file mode 100644 index 00000000..196661a7 --- /dev/null +++ b/src/authsome/ui/web/.gitkeep @@ -0,0 +1 @@ +Generated static UI assets are copied here by scripts/build-ui.sh. diff --git a/tests/proxy/test_proxy.py b/tests/proxy/test_proxy.py index 292095ed..d078a119 100644 --- a/tests/proxy/test_proxy.py +++ b/tests/proxy/test_proxy.py @@ -567,7 +567,7 @@ async def test_addon_denies_no_credentials_with_provider_hint_in_configured_deny body = flow.response.content.decode("utf-8") assert "openai" in body assert "authsome login openai" in body - assert f"{DEFAULT_SERVER_BASE_URL}/apps/openai" in body + assert f"{DEFAULT_SERVER_BASE_URL}/" in body events = [call.args[0] for call in auth.record_audit_event.await_args_list] assert { "event": "proxy_no_credentials", diff --git a/tests/server/test_ui_dashboard.py b/tests/server/test_ui_dashboard.py deleted file mode 100644 index cee00c55..00000000 --- a/tests/server/test_ui_dashboard.py +++ /dev/null @@ -1,548 +0,0 @@ -import asyncio -from datetime import timedelta -from pathlib import Path -from urllib.parse import urlparse - -from fastapi.testclient import TestClient - -from authsome import audit -from authsome.auth.models.connection import ConnectionRecord, ProviderClientRecord, ProviderMetadataRecord -from authsome.auth.models.enums import AuthType, ConnectionStatus -from authsome.identity import create_identity, load_private_key -from authsome.identity.proof import create_proof_jwt -from authsome.server.app import create_app -from authsome.server.credential_repository import build_store_key -from authsome.utils import utc_now - - -def _auth_header(tmp_path: Path, method: str, path: str, *, handle: str) -> dict[str, str]: - identity = create_identity(tmp_path, handle) - token = create_proof_jwt( - private_key=load_private_key(tmp_path, identity.handle), - issuer=identity.did, - subject=identity.handle, - method=method, - path_query=path, - body=b"", - ) - return {"Authorization": f"PoP {token}"} - - -def _register_identity_for_claim(client: TestClient, tmp_path: Path, handle: str) -> str: - identity = create_identity(tmp_path, handle) - response = client.post("/identities/register", json={"handle": identity.handle, "did": identity.did}) - assert response.status_code == 200 - return urlparse(response.json()["claim_url"]).path - - -def _register_identity(client: TestClient, tmp_path: Path, handle: str, *, email: str = "dev@example.com") -> None: - """Register an identity and drive the browser claim flow, leaving the client logged in.""" - claim_path = _register_identity_for_claim(client, tmp_path, handle) - registered = client.post( - "/auth/register", - data={"email": email, "password": "password-1", "next": claim_path}, - follow_redirects=False, - ) - assert registered.status_code == 303 - assert client.post(f"{claim_path}/confirm", follow_redirects=False).status_code == 303 - - -def _seed_connection( - client: TestClient, - *, - identity: str, - provider: str, - auth_type: AuthType, - connection_name: str = "default", - access_token: str | None = None, - refresh_token: str | None = None, - api_key: str | None = None, -) -> None: - resolved = asyncio.run(client.app.state.ownership_resolver.resolve(identity=identity)) - record = ConnectionRecord( - provider=provider, - identity=identity, - principal_id=resolved.principal_id, - vault_id=resolved.vault_id, - connection_name=connection_name, - auth_type=auth_type, - status=ConnectionStatus.CONNECTED, - access_token=access_token, - refresh_token=refresh_token, - api_key=api_key, - expires_at=utc_now() + timedelta(hours=1), - ) - asyncio.run( - client.app.state.vault.put( - build_store_key( - vault=resolved.vault_id, - provider=provider, - record_type="connection", - connection=connection_name, - ), - record.model_dump_json(), - collection=f"vault:{resolved.vault_id}", - ) - ) - asyncio.run( - client.app.state.vault.put( - build_store_key(vault=resolved.vault_id, provider=provider, record_type="metadata"), - ProviderMetadataRecord( - identity=identity, - principal_id=resolved.principal_id, - vault_id=resolved.vault_id, - provider=provider, - connection_names=[connection_name], - default_connection=connection_name, - last_used_connection=connection_name, - ).model_dump_json(), - collection=f"vault:{resolved.vault_id}", - ) - ) - - -def _seed_provider_client( - client: TestClient, - *, - provider: str, - client_id: str, - client_secret: str | None = None, -) -> None: - from authsome.auth.models.connection import ProviderClientRecord - - asyncio.run( - client.app.state.vault.put( - build_store_key(provider=provider, record_type="server"), - ProviderClientRecord( - provider=provider, - client_id=client_id, - client_secret=client_secret, - ).model_dump_json(), - collection="server", - ) - ) - - -def test_overview_navigation_shows_applications_connections_and_identity(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - response = client.get("/") - - assert response.status_code == 200 - assert "Overview" in response.text - assert "Applications" in response.text - assert "Connections" in response.text - assert "Identity" in response.text - assert 'href="/audit"' in response.text - - -def test_applications_page_renders_provider_catalog(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - response = client.get("/applications") - - assert response.status_code == 200 - assert "Applications" in response.text - - -def test_applications_page_shows_provider_login_action(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - response = client.get("/applications") - - assert response.status_code == 200 - assert 'action="/apps/github/connect"' in response.text - assert "Login" in response.text - - -def test_identity_page_renders_informational_identity_view(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - response = client.get("/identity") - - assert response.status_code == 200 - assert "Identity" in response.text - assert "steady-wisely-boldly-0042" in response.text - - -def test_account_identity_page_lists_all_account_claims(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - first_claim = _register_identity_for_claim(client, tmp_path, "steady-wisely-boldly-0042") - registered = client.post( - "/auth/register", - data={"email": "dev@example.com", "password": "password-1", "next": first_claim}, - follow_redirects=False, - ) - assert registered.status_code == 303 - assert client.post(f"{first_claim}/confirm", follow_redirects=False).status_code == 303 - - second_claim = _register_identity_for_claim(client, tmp_path, "brave-softly-surely-0043") - assert "dev@example.com" in client.get(second_claim).text - assert client.post(f"{second_claim}/confirm", follow_redirects=False).status_code == 303 - - response = client.get("/identity") - - assert response.status_code == 200 - assert "Signed in as dev@example.com" in response.text - assert "steady-wisely-boldly-0042" in response.text - assert "brave-softly-surely-0043" in response.text - - -def test_audit_page_renders_recent_events_for_admin(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - audit.emit_event( - "credentials_exported", - source="external", - identity="steady-wisely-boldly-0042", - provider="github", - status="ok", - request_id="req-123", - ) - response = client.get("/audit") - - assert response.status_code == 200 - assert "Audit Log" in response.text - assert "Credentials exported" in response.text - assert "steady-wisely-boldly-0042" in response.text - assert "github" in response.text - assert "req-123" in response.text - - -def test_non_admin_ui_hides_audit_and_provider_configuration(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "admin-steadily-surely-0041", email="admin@example.com") - _register_identity(client, tmp_path, "user-steadily-surely-0042", email="user@example.com") - _seed_provider_client(client, provider="github", client_id="cid-123", client_secret="secret-123") - - overview = client.get("/") - provider = client.get("/apps/github") - configure = client.post("/apps/github/configure", follow_redirects=False) - audit_page = client.get("/audit") - - assert overview.status_code == 200 - assert 'href="/audit"' not in overview.text - assert provider.status_code == 200 - assert 'action="/apps/github/configure"' not in provider.text - assert "Client ID" not in provider.text - assert configure.status_code == 403 - assert "Provider configuration is available only to administrators." in configure.text - assert audit_page.status_code == 403 - assert "Audit events are available only to administrators." in audit_page.text - - -def test_account_applications_redirects_to_ui_login_entry(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - response = client.get("/applications", follow_redirects=False) - - assert response.status_code == 303 - assert response.headers["location"] == "/?next=%2Fapplications" - - -def test_provider_page_shows_provider_configuration_not_connection_tokens(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="github", - auth_type=AuthType.OAUTH2, - access_token="gh-access-token", - refresh_token="gh-refresh-token", - ) - response = client.get("/apps/github") - - assert response.status_code == 200 - assert "OAuth Application" in response.text or "Managed by Authsome" in response.text - assert "Access Token" not in response.text - - -def test_named_connection_detail_route_exists(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="github", - auth_type=AuthType.OAUTH2, - access_token="gh-access-token", - refresh_token="gh-refresh-token", - ) - response = client.get("/apps/github/connections/default") - - assert response.status_code == 200 - - -def test_named_connection_detail_page_shows_oauth_tokens(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="github", - auth_type=AuthType.OAUTH2, - access_token="gh-access-token", - refresh_token="gh-refresh-token", - ) - response = client.get("/apps/github/connections/default") - - assert response.status_code == 200 - assert "Access Token" in response.text - assert "Refresh Token" in response.text - assert "Client ID" not in response.text - assert "Client Secret" not in response.text - assert "Redirect URI" not in response.text - assert "Authorization URL" not in response.text - assert "Token URL" not in response.text - - -def test_named_connection_detail_page_shows_api_key(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="openai", - auth_type=AuthType.API_KEY, - api_key="sk-test-key", - ) - response = client.get("/apps/openai/connections/default") - - assert response.status_code == 200 - assert "API Credentials" in response.text - - -def test_provider_page_for_api_key_provider_omits_provider_setup_section(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="openai", - auth_type=AuthType.API_KEY, - api_key="sk-test-key", - ) - response = client.get("/apps/openai") - - assert response.status_code == 200 - assert "OAuth Application" not in response.text - assert "API Credentials" not in response.text - - -def test_provider_page_lists_existing_connections_as_read_only_context(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="github", - auth_type=AuthType.OAUTH2, - access_token="gh-access-token", - refresh_token="gh-refresh-token", - ) - response = client.get("/apps/github") - - assert response.status_code == 200 - assert "Existing connections" in response.text - - -def test_connections_page_renders_connection_rows(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="github", - auth_type=AuthType.OAUTH2, - access_token="gh-access-token", - refresh_token="gh-refresh-token", - ) - response = client.get("/manage/connections") - - assert response.status_code == 200 - assert "Add new connection" in response.text - assert "connection-row" in response.text - - -def test_provider_login_modal_copy_is_rendered_when_default_exists(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="github", - auth_type=AuthType.OAUTH2, - access_token="gh-access-token", - refresh_token="gh-refresh-token", - ) - response = client.get("/applications") - - assert response.status_code == 200 - assert "Connection name" in response.text - - -def test_connect_app_accepts_connection_name_fallback(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="github", - auth_type=AuthType.OAUTH2, - access_token="gh-access-token", - refresh_token="gh-refresh-token", - ) - response = client.post( - "/apps/github/connect", - data={"connection_name": "work"}, - follow_redirects=False, - ) - - assert response.status_code == 303 - assert "/auth/sessions/" in response.headers["location"] - assert any( - session.provider == "github" and session.connection_name == "work" - for session in client.app.state.auth_sessions._sessions.values() - ) - - -def test_provider_page_shows_configure_action_for_oauth(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_provider_client(client, provider="github", client_id="cid-123", client_secret="secret-123") - response = client.get("/apps/github") - - assert response.status_code == 200 - assert 'action="/apps/github/configure"' in response.text - assert "Replace" in response.text - - -def test_provider_configure_route_opens_edit_flow_with_existing_values(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_provider_client(client, provider="github", client_id="cid-123", client_secret="secret-123") - response = client.post("/apps/github/configure", follow_redirects=False) - - assert response.status_code == 303 - assert "/auth/sessions/" in response.headers["location"] - session = next(iter(client.app.state.auth_sessions._sessions.values())) - assert session.payload["provider_config_only"] is True - fields = session.payload["input_fields"] - assert any(field["name"] == "client_id" and field["default"] == "cid-123" for field in fields) - - -def test_account_admin_provider_configure_route_opens_edit_flow(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - claim_path = _register_identity_for_claim(client, tmp_path, "steady-wisely-boldly-0042") - registered = client.post( - "/auth/register", - data={"email": "dev@example.com", "password": "password-1", "next": claim_path}, - follow_redirects=False, - ) - assert registered.status_code == 303 - assert client.post(f"{claim_path}/confirm", follow_redirects=False).status_code == 303 - - _seed_provider_client(client, provider="github", client_id="cid-123", client_secret="secret-123") - response = client.post("/apps/github/configure", follow_redirects=False) - - assert response.status_code == 303 - assert "/auth/sessions/" in response.headers["location"] - session = next(iter(client.app.state.auth_sessions._sessions.values())) - assert session.payload["provider_config_only"] is True - fields = session.payload["input_fields"] - assert any(field["name"] == "client_id" and field["default"] == "cid-123" for field in fields) - - -def test_provider_configure_input_page_shows_revoke_warning(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_provider_client(client, provider="github", client_id="cid-123", client_secret="secret-123") - configure = client.post("/apps/github/configure", follow_redirects=False) - response = client.get(configure.headers["location"]) - - assert response.status_code == 200 - assert "Changing these credentials will revoke existing connections for this provider." in response.text - client_id_position = response.text.index('for="client_id">Client ID') - client_secret_position = response.text.index('for="client_secret">Client Secret') - advanced_position = response.text.index("
Advanced options") - assert client_id_position < client_secret_position < advanced_position - assert "Client Secret (Optional)" not in response.text - - -def test_provider_config_submit_replaces_client_and_revokes_connections(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - _seed_connection( - client, - identity="steady-wisely-boldly-0042", - provider="github", - auth_type=AuthType.OAUTH2, - access_token="gh-access-token", - refresh_token="gh-refresh-token", - ) - _seed_provider_client(client, provider="github", client_id="cid-123", client_secret="secret-123") - - configure = client.post("/apps/github/configure", follow_redirects=False) - session_id = configure.headers["location"].rstrip("/").split("/")[-2] - response = client.post( - f"/auth/sessions/{session_id}/input", - data={"client_id": "cid-456", "client_secret": "secret-456"}, - follow_redirects=False, - ) - - provider_client = asyncio.run( - client.app.state.vault.get(build_store_key(provider="github", record_type="server"), collection="server") - ) - connections_page = client.get("/manage/connections") - - assert response.status_code == 303 - assert response.headers["location"].endswith("/apps/github") - assert provider_client is not None - provider_client_record = ProviderClientRecord.model_validate_json(provider_client) - assert provider_client_record.client_id == "cid-456" - assert provider_client_record.scopes == ["repo", "read:user"] - assert "No connections yet" in connections_page.text diff --git a/tests/server/test_ui_sessions.py b/tests/server/test_ui_sessions.py deleted file mode 100644 index a2cb3721..00000000 --- a/tests/server/test_ui_sessions.py +++ /dev/null @@ -1,279 +0,0 @@ -import asyncio -from pathlib import Path -from urllib.parse import urlparse - -from fastapi.testclient import TestClient - -from authsome.auth.models.connection import ProviderClientRecord -from authsome.identity import create_identity, load_private_key -from authsome.identity.proof import create_proof_jwt -from authsome.server.app import create_app -from authsome.server.credential_repository import build_store_key -from authsome.server.ui_sessions import UiSessionStore - - -def _auth_header(tmp_path: Path, method: str, path: str, *, handle: str) -> dict[str, str]: - identity = create_identity(tmp_path, handle) - token = create_proof_jwt( - private_key=load_private_key(tmp_path, identity.handle), - issuer=identity.did, - subject=identity.handle, - method=method, - path_query=path, - body=b"", - ) - return {"Authorization": f"PoP {token}"} - - -def _register_identity(client: TestClient, tmp_path: Path, handle: str) -> None: - identity = create_identity(tmp_path, handle) - response = client.post("/identities/register", json={"handle": identity.handle, "did": identity.did}) - assert response.status_code == 200 - - -def _seed_provider_client( - client: TestClient, - *, - provider: str, - client_id: str, - client_secret: str | None = None, -) -> None: - asyncio.run( - client.app.state.vault.put( - build_store_key(provider=provider, record_type="server"), - ProviderClientRecord( - provider=provider, - client_id=client_id, - client_secret=client_secret, - ).model_dump_json(), - collection="server", - ) - ) - - -def _claim_identity_via_account_ui(client: TestClient, tmp_path: Path, handle: str, email: str) -> None: - identity = create_identity(tmp_path, handle) - response = client.post("/identities/register", json={"handle": identity.handle, "did": identity.did}) - assert response.status_code == 200 - claim_path = urlparse(response.json()["claim_url"]).path - registered = client.post( - "/auth/register", - data={"email": email, "password": "password-1", "next": claim_path}, - follow_redirects=False, - ) - assert registered.status_code == 303 - confirmed = client.post(f"{claim_path}/confirm", follow_redirects=False) - assert confirmed.status_code == 303 - - -def test_create_pending_claim_and_consume_once() -> None: - store = UiSessionStore("test-secret") - - token = store.create_pending_claim(identity="steady-wisely-boldly-0042") - resolved = store.get_pending_claim(token.token) - consumed = store.consume_pending_claim(token.token) - - assert resolved.identity == "steady-wisely-boldly-0042" - assert consumed.identity == "steady-wisely-boldly-0042" - - -def test_consume_pending_claim_rejects_reuse() -> None: - store = UiSessionStore("test-secret") - - token = store.create_pending_claim(identity="steady-wisely-boldly-0042") - store.consume_pending_claim(token.token) - - try: - store.consume_pending_claim(token.token) - except KeyError: - pass - else: - raise AssertionError("Expected pending claim token reuse to fail") - - -def test_build_cookie_round_trips_browser_session() -> None: - store = UiSessionStore("test-secret") - - session = store.create_browser_session(principal_id="principal_123", email="dev@example.com") - cookie_value = store.build_cookie_value(session.token) - parsed = store.get_browser_session(cookie_value) - - assert parsed.principal_id == "principal_123" - assert parsed.email == "dev@example.com" - - -def test_account_ui_homepage_shows_auth_tabs(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - response = client.get("/") - - assert response.status_code == 200 - assert "Open dashboard" in response.text - assert "Sign in" in response.text - assert "Create account" in response.text - - -def test_account_claim_page_shows_auth_tabs_for_unauthenticated_users(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - identity = create_identity(tmp_path, "steady-wisely-boldly-0042") - response = client.post("/identities/register", json={"handle": identity.handle, "did": identity.did}) - assert response.status_code == 200 - claim_path = urlparse(response.json()["claim_url"]).path - - claim_response = client.get(claim_path) - - assert claim_response.status_code == 200 - assert "Sign in" in claim_response.text - assert "Create account" in claim_response.text - - -def test_account_ui_session_returns_dashboard_url_without_browser_cookie(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _claim_identity_via_account_ui(client, tmp_path, "steady-wisely-boldly-0042", "dev@example.com") - bootstrap_response = client.post( - "/session", - headers=_auth_header(tmp_path, "POST", "/session", handle="steady-wisely-boldly-0042"), - ) - assert bootstrap_response.status_code == 200 - assert urlparse(bootstrap_response.json()["url"]).path == "/" - assert "authsome_ui_session=" not in bootstrap_response.headers.get("set-cookie", "") - - client.cookies.clear() - dashboard_response = client.get("/") - - assert dashboard_response.status_code == 200 - assert "Open dashboard" in dashboard_response.text - - -def test_account_homepage_registration_redirects_to_dashboard(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - registered = client.post( - "/auth/register", - data={"email": "dev@example.com", "password": "password-1", "next": "/"}, - follow_redirects=False, - ) - dashboard_response = client.get("/") - - assert registered.status_code == 303 - assert registered.headers["location"] == "/" - assert dashboard_response.status_code == 200 - assert "Overview" in dashboard_response.text - assert "Signed in as dev@example.com" in dashboard_response.text - - -def test_account_ui_hides_server_managed_oauth_client_details(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _claim_identity_via_account_ui(client, tmp_path, "admin-ready-boldly-0001", "admin@example.com") - _claim_identity_via_account_ui(client, tmp_path, "steady-wisely-boldly-0042", "dev@example.com") - vault = client.app.state.vault - key = build_store_key(provider="github", record_type="server") - record = ProviderClientRecord(provider="github", client_id="cid-123", client_secret="top-secret") - asyncio.run(vault.put(key, record.model_dump_json(), collection="server")) - - response = client.get("/apps/github") - - assert response.status_code == 200 - assert "cid-123" not in response.text - assert "manages the OAuth application" in response.text - assert "Existing connections" not in response.text - - -def test_account_admin_ui_shows_provider_client_details(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _claim_identity_via_account_ui(client, tmp_path, "steady-wisely-boldly-0042", "dev@example.com") - _seed_provider_client(client, provider="github", client_id="cid-123", client_secret="top-secret") - response = client.get("/apps/github") - - assert response.status_code == 200 - assert "cid-123" in response.text - assert "Existing connections" in response.text - assert "manages the OAuth application" not in response.text - assert 'action="/apps/github/configure"' in response.text - - -def test_account_ui_connect_starts_principal_scoped_session_without_pop(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - _claim_identity_via_account_ui(client, tmp_path, "steady-wisely-boldly-0042", "dev@example.com") - - response = client.post("/apps/openai/connect", follow_redirects=False) - session = next(iter(client.app.state.auth_sessions._sessions.values())) - - assert response.status_code == 303 - assert "/auth/sessions/" in response.headers["location"] - assert session.identity is None - assert session.principal_id is not None - assert session.payload["ui_session_required"] is True - - -def test_account_auth_rejects_external_next_redirect(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - response = client.post( - "/auth/register", - data={"email": "dev@example.com", "password": "password-1", "next": "https://example.test"}, - follow_redirects=False, - ) - - assert response.status_code == 303 - assert response.headers["location"] == "/" - - -def test_account_homepage_login_error_renders_auth_page(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - with TestClient(create_app()) as client: - client.post( - "/auth/register", - data={"email": "dev@example.com", "password": "password-1", "next": "/"}, - follow_redirects=False, - ) - client.cookies.clear() - response = client.post( - "/auth/login", - data={"email": "dev@example.com", "password": "wrong-password", "next": "/"}, - follow_redirects=False, - ) - - assert response.status_code == 400 - assert "Invalid email or password" in response.text - assert "Sign in" in response.text - assert "Create account" in response.text - - -def test_account_ui_auth_input_requires_matching_browser_session(monkeypatch, tmp_path: Path) -> None: - monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path)) - - app = create_app() - with TestClient(app) as client: - _register_identity(client, tmp_path, "steady-wisely-boldly-0042") - session = asyncio.run( - client.app.state.auth_sessions.create( - provider="github", - identity="steady-wisely-boldly-0042", - principal_id="principal_test", - connection_name="default", - flow_type="pkce", - ) - ) - session.payload["ui_session_required"] = True - session.payload["input_fields"] = [{"name": "client_id", "label": "Client ID", "secret": False}] - asyncio.run(client.app.state.auth_sessions.save(session)) - - response = client.get(f"/auth/sessions/{session.session_id}/input") - - assert response.status_code == 401 - assert "dashboard" in response.text diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 00000000..cd7c35c7 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,44 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +!/src/lib/ +!/src/lib/** + +# vercel +.vercel + +# typescript +*.tsbuildinfo +!next-env.d.ts diff --git a/ui/AGENTS.md b/ui/AGENTS.md new file mode 100644 index 00000000..8bd0e390 --- /dev/null +++ b/ui/AGENTS.md @@ -0,0 +1,5 @@ + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + diff --git a/ui/CLAUDE.md b/ui/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/ui/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/ui/DESIGN.md b/ui/DESIGN.md new file mode 100644 index 00000000..97f7c5f2 --- /dev/null +++ b/ui/DESIGN.md @@ -0,0 +1,198 @@ +--- +name: Authsome Secure Console +colors: + surface: '#131315' + surface-dim: '#0e0e10' + surface-bright: '#2a2a2c' + surface-container-lowest: '#0e0e10' + surface-container-low: '#1c1b1d' + surface-container: '#201f22' + surface-container-high: '#2a2a2c' + surface-container-highest: '#353437' + on-surface: '#e5e1e4' + on-surface-variant: '#bbcabf' + inverse-surface: '#e5e1e4' + inverse-on-surface: '#313032' + outline: '#86948a' + outline-variant: '#3c4a42' + surface-tint: '#4edea3' + primary: '#4edea3' + on-primary: '#003824' + primary-container: '#10b981' + on-primary-container: '#00422b' + inverse-primary: '#006c49' + secondary: '#a6d1ad' + on-secondary: '#10381f' + secondary-container: '#284f33' + on-secondary-container: '#95bf9d' + tertiary: '#bcc7de' + on-tertiary: '#263143' + tertiary-container: '#98a3ba' + on-tertiary-container: '#2e394c' + error: '#ffb4ab' + on-error: '#690005' + error-container: '#93000a' + on-error-container: '#ffdad6' + primary-fixed: '#6ffbbe' + primary-fixed-dim: '#4edea3' + on-primary-fixed: '#002113' + on-primary-fixed-variant: '#005236' + secondary-fixed: '#c1edc8' + secondary-fixed-dim: '#a6d1ad' + on-secondary-fixed: '#00210d' + on-secondary-fixed-variant: '#284f33' + tertiary-fixed: '#d8e3fb' + tertiary-fixed-dim: '#bcc7de' + on-tertiary-fixed: '#111c2d' + on-tertiary-fixed-variant: '#3c475a' + background: '#131315' + on-background: '#e5e1e4' + surface-variant: '#353437' +typography: + headline-lg: + fontFamily: Hanken Grotesk + fontSize: 30px + fontWeight: '600' + lineHeight: 36px + letterSpacing: -0.02em + headline-md: + fontFamily: Hanken Grotesk + fontSize: 24px + fontWeight: '600' + lineHeight: 32px + letterSpacing: -0.01em + body-lg: + fontFamily: Hanken Grotesk + fontSize: 16px + fontWeight: '400' + lineHeight: 24px + body-sm: + fontFamily: Hanken Grotesk + fontSize: 14px + fontWeight: '400' + lineHeight: 20px + code-md: + fontFamily: JetBrains Mono + fontSize: 14px + fontWeight: '450' + lineHeight: 20px + code-sm: + fontFamily: JetBrains Mono + fontSize: 12px + fontWeight: '400' + lineHeight: 16px + label-caps: + fontFamily: JetBrains Mono + fontSize: 11px + fontWeight: '700' + lineHeight: 16px + letterSpacing: 0.05em +rounded: + sm: 0.125rem + DEFAULT: 0.25rem + md: 0.375rem + lg: 0.5rem + xl: 0.75rem + full: 9999px +spacing: + margin-page: 2rem + gutter-grid: 1rem + stack-sm: 0.5rem + stack-md: 1.5rem + container-width: 1200px +--- + +## Brand & Style + +The brand identity centers on **Secure Developer Console**: the calm, precise feeling of a well-instrumented control plane for agent credentials. It is built for engineers, platform teams, and security reviewers who need to trust the product before they try it. + +The visual style is **minimal infrastructure with quiet depth**. It keeps shadcn component discipline, but avoids a page made of identical boxes. Sections should feel like layered panels, terminal surfaces, request traces, and security status bands rather than marketing cards. Gradients are allowed only as low-opacity light, edge glow, or inspection depth; they should never dominate the palette. + +The strongest emotional signals are: + +- **Developer-focused:** terminal fragments, command affordances, monospace metadata, request/audit language. +- **Trustworthy:** stable layout, restrained contrast, no bouncy motion, no over-decorated cards. +- **Secure:** emerald status accents, crisp boundaries, explicit policy/audit/vault language, subtle glow only around protected or verified states. +- **Friendly:** clear hierarchy, readable copy, obvious actions, enough whitespace for scanning. + +## Colors + +The palette is anchored by **Deep Emerald** and **Obsidian**. We use a dark-first strategy to reduce eye strain for technical work and to evoke a code editor/control plane. + +- **Primary:** Emerald (#10B981) used sparingly for successful states, primary actions, verified markers, and active request paths. It represents secure access, not decoration. +- **Secondary:** A deep Forest Green (#052E16) used for subtle backgrounds on active navigation items or success-themed containers. +- **Neutral/Background:** We use a true Obsidian (#09090B) for the primary background to maximize contrast with borders. +- **Accents:** Slate, zinc, and muted blue-gray tones support borders, secondary text, and architecture diagrams. Use them to create confidence without making the page monochrome. + +## Typography + +The typography system uses a dual-font approach to distinguish between "Interface" and "Data." + +**Hanken Grotesk** serves as the primary UI font. It provides a sharp, contemporary sans-serif look that is highly legible at small sizes. Headings use tighter letter-spacing and heavier weights to feel structural. + +**JetBrains Mono** is used for all "output" and system-related data, including IDs, terminal logs, audit event details, and credential strings. This distinction helps developers mentally categorize information: sans-serif is what the app is telling them, and monospace is the data they are managing. All labels for status or metadata use the `label-caps` role to mimic the appearance of a command-line header. + +## Layout & Spacing + +The layout uses a **fixed-fluid hybrid** grid. Marketing pages should feel like product surfaces, not brochure sections: wide enough for technical detail, constrained enough for scanability. + +- **Navigation:** Sticky, compact, and utility-like. The brand should read as a product in the first viewport. +- **Content:** Uses a 1rem gutter between elements. Information is grouped into panels, rows, terminal surfaces, and connected grids rather than repeated boxes. +- **Rhythm:** We follow a 4px base unit. Spacing between related items (label to input) is 8px, while spacing between unrelated sections is 24px or 32px. +- **Mobile:** On mobile devices, the grid collapses to a single column, the sidebar becomes a bottom sheet or a hidden menu, and page margins reduce to 1rem. + +## Elevation, Depth & Animations + +This design system avoids high-elevation shadows in favor of **Tonal Layering**, **Crisp Outlines**, and **Subtle Glows (Linear-style)**. + +Depth is achieved through background contrast, hairline borders, and very soft gradients: + +- **Level 0 (Base):** The primary app background (#09090B), with a subtle grid/cross texture. +- **Level 1 (Panel/Section):** A slightly lighter shade (#131315) with a subtle 1px border (#27272A). +- **Level 2 (Terminal/Inspector):** A higher contrast surface (#0E0E10 or #1C1B1D), internal dividers, and a soft ambient shadow. +- **Gradient Use:** Emerald gradients should sit at section edges, terminal glows, or hover borders at 5-12% opacity. They should imply protected flow, not decoration. + +Shadows, when used, are strictly ambient: no heavy offset, large blur, low opacity. Interaction feedback is represented by changing the border color, surface tone, or very subtle glow. Avoid scaling cards and buttons; the product should feel steady. + +**Animations & Micro-interactions:** +- **Linear-style motion:** Animations should be minimal, spring-based or smooth ease-outs using Framer Motion. +- Elements should fade in and slide up slightly (`y: 20` to `0`) as they enter the viewport. +- Staggered entrances for list items and grid cards. +- Hover states should include a subtle ease-in-out glow or border transition. Elements should feel responsive but not bouncy. + +## Shapes + +The shape language is **Soft-Technical**. Use small radii and occasional open or split panels so the UI does not become boxy. + +- **Inputs & Buttons:** 4px to 6px radius to provide just enough approachability. +- **Containers:** 8px radius for primary panels. Avoid large rounded cards unless they are terminal or inspector surfaces. +- **Pills:** Status badges (e.g., "Active", "Success") use a fully rounded/pill shape to distinguish them from interactive buttons. +- **Section Grids:** Prefer connected panels, timeline rows, split layouts, and hairline dividers over repeating boxed cards. + +## Components + +### Buttons +- **Primary:** Solid #10B981 with black text. No gradient. High contrast. +- **Secondary:** Transparent background with a 1px border (#27272A) and white text. +- **Ghost:** No border or background unless hovered. + +### Input Fields +- Dark backgrounds (#09090B) with 1px borders. Focus state should change the border color to #10B981 with a subtle emerald outer glow (2px). Labels should be in `code-sm` or `body-sm`. + +### Cards & Panels +- Cards must have a 1px border, but repeated cards should not all have the same box silhouette. Use connected grids, split panels, list rows, or asymmetric feature panels where possible. +- Use tonal surfaces over heavy shadows. A card hover can reveal a faint emerald edge or gradient, but should not jump or scale. + +### Status Chips +- Use the `code-sm` font. Backgrounds should be low-saturation (e.g., a very dark red for "Error" with light red text) to ensure the interface doesn't become too "noisy." + +### Code Blocks +- Use a distinct background (#000000) with a 1px emerald border left-accent. Use JetBrains Mono for the content. + +### Homepage Sections +- **Hero:** Must immediately communicate developer trust: install command, source link, runtime credential injection, and a real terminal/agent moment. +- **Incident Proof:** Should feel like a security briefing, not blog cards. Use compact evidence panels with source metadata. +- **Problem/Context:** Use a high-readability editorial block plus a connected claim grid. +- **Features:** Should look like capability inventory for platform engineers. Numbering, labels, and borders matter. +- **Audience:** Should feel human and clear, with less box weight than feature inventory. +- **Architecture:** Should read like a layered control plane, with terminal/trace language and calm state indicators. diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 00000000..e215bc4c --- /dev/null +++ b/ui/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/ui/components.json b/ui/components.json new file mode 100644 index 00000000..8d886db5 --- /dev/null +++ b/ui/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "base-nova", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} +} diff --git a/ui/eslint.config.mjs b/ui/eslint.config.mjs new file mode 100644 index 00000000..05e726d1 --- /dev/null +++ b/ui/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/ui/next-env.d.ts b/ui/next-env.d.ts new file mode 100644 index 00000000..9edff1c7 --- /dev/null +++ b/ui/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/ui/next.config.ts b/ui/next.config.ts new file mode 100644 index 00000000..a18b38eb --- /dev/null +++ b/ui/next.config.ts @@ -0,0 +1,8 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + output: "export", + trailingSlash: true, +}; + +export default nextConfig; diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 00000000..d05cd1b8 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,34 @@ +{ + "name": "ui", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "@base-ui/react": "^1.5.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.17.0", + "next": "16.2.6", + "react": "19.2.4", + "react-dom": "19.2.4", + "shadcn": "^4.9.0", + "swr": "^2.4.1", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.6", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml new file mode 100644 index 00000000..0daf3572 --- /dev/null +++ b/ui/pnpm-lock.yaml @@ -0,0 +1,6246 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@base-ui/react': + specifier: ^1.5.0 + version: 1.5.0(@types/react@19.2.15)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^1.17.0 + version: 1.17.0(react@19.2.4) + next: + specifier: 16.2.6 + version: 16.2.6(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: + specifier: 19.2.4 + version: 19.2.4 + react-dom: + specifier: 19.2.4 + version: 19.2.4(react@19.2.4) + shadcn: + specifier: ^4.9.0 + version: 4.9.0(@types/node@20.19.41)(typescript@5.9.3) + swr: + specifier: ^2.4.1 + version: 2.4.1(react@19.2.4) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + devDependencies: + '@tailwindcss/postcss': + specifier: ^4 + version: 4.3.0 + '@types/node': + specifier: ^20 + version: 20.19.41 + '@types/react': + specifier: ^19 + version: 19.2.15 + '@types/react-dom': + specifier: ^19 + version: 19.2.3(@types/react@19.2.15) + eslint: + specifier: ^9 + version: 9.39.4(jiti@2.7.0) + eslint-config-next: + specifier: 16.2.6 + version: 16.2.6(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + tailwindcss: + specifier: ^4 + version: 4.3.0 + typescript: + specifier: ^5 + version: 5.9.3 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.29.7': + resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@base-ui/react@1.5.0': + resolution: {integrity: sha512-z1gSAlced1yY+iM+mHDEtIkD8UI3Ebs52MuBPxvV6f5hRutk+xvCH/wuB7hDqDzK9JG5FoMz5nhrqtSs1wjt1A==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@date-fns/tz': ^1.2.0 + '@types/react': ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@date-fns/tz': + optional: true + '@types/react': + optional: true + date-fns: + optional: true + + '@base-ui/utils@0.2.9': + resolution: {integrity: sha512-x/PDDCYzoqPpjrdyb3VcyylTI2IjUXEtYDGi5foh7KsnmNJIIaVwA2GLgDH1dps1GgXiJbA60hM+AyuTfQzIvw==} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + '@dotenvx/dotenvx@1.70.0': + resolution: {integrity: sha512-vC/rom87ym8HEyVdzZZS6/PYGg1Z5fmozUZ8l6cw1sYAxdL1lEyvE/JbK8cMFQoq3GsR/P1PiQRY+VXMtDN9bw==} + hasBin: true + + '@ecies/ciphers@0.2.6': + resolution: {integrity: sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==} + engines: {bun: '>=1', deno: '>=2.7.10', node: '>=16'} + peerDependencies: + '@noble/ciphers': ^1.0.0 + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@mswjs/interceptors@0.41.9': + resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} + engines: {node: '>=18'} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@next/env@16.2.6': + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} + + '@next/eslint-plugin-next@16.2.6': + resolution: {integrity: sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw==} + + '@next/swc-darwin-arm64@16.2.6': + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.2.6': + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.2.6': + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-arm64-musl@16.2.6': + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@next/swc-linux-x64-gnu@16.2.6': + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-x64-musl@16.2.6': + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@next/swc-win32-arm64-msvc@16.2.6': + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.2.6': + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/deferred-promise@3.0.0': + resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} + + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + engines: {node: '>= 20'} + + '@tailwindcss/postcss@4.3.0': + resolution: {integrity: sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==} + + '@ts-morph/common@0.27.0': + resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/node@20.19.41': + resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/validate-npm-package-name@4.0.2': + resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} + + '@typescript-eslint/eslint-plugin@8.60.0': + resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.60.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.60.0': + resolution: {integrity: sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.60.0': + resolution: {integrity: sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.60.0': + resolution: {integrity: sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.60.0': + resolution: {integrity: sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.60.0': + resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.60.0': + resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.60.0': + resolution: {integrity: sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.60.0': + resolution: {integrity: sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.60.0': + resolution: {integrity: sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} + cpu: [x64] + os: [win32] + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.11.4: + resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.33: + resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} + engines: {node: '>=6.0.0'} + hasBin: true + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eciesjs@0.4.18: + resolution: {integrity: sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.364: + resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.22.1: + resolution: {integrity: sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==} + engines: {node: '>=10.13.0'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.3.2: + resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@16.2.6: + resolution: {integrity: sha512-z2ELYSkyrrJ6cuunTU8vhsT/RpouPkjaSah06nVW6Rg2Hpg0Vs8s497/e5s8G8qtdp4ccsiovz5P1rv+5VSW2Q==} + peerDependencies: + eslint: '>=9.0.0' + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.13.0: + resolution: {integrity: sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@11.3.5: + resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} + engines: {node: '>=14.14'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + fuzzysort@3.1.0: + resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-own-enumerable-keys@1.0.0: + resolution: {integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==} + engines: {node: '>=14.16'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.4.0: + resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphql@16.14.0: + resolution: {integrity: sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + headers-polyfill@5.0.1: + resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + hono@4.12.23: + resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@3.0.0: + resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} + engines: {node: '>=12'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-regexp@3.1.0: + resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} + engines: {node: '>=12'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@1.17.0: + resolution: {integrity: sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.14.6: + resolution: {integrity: sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + next@16.2.6: + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object-treeify@1.1.33: + resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} + engines: {node: '>= 10'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + reselect@5.2.0: + resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rettime@0.11.11: + resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shadcn@4.9.0: + resolution: {integrity: sha512-GPrj/bFcxxykkDzHRDNzoJMAS1a6M4IcfSWpxKU7FXx7DzBU7QumZM9roovo0Blw/z6wRRl7moDB6jnreOFFGA==} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + stringify-object@5.0.0: + resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} + engines: {node: '>=14.16'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swr@2.4.1: + resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tldts-core@7.4.2: + resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==} + + tldts@7.4.2: + resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-morph@26.0.0: + resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@5.7.0: + resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} + engines: {node: '>=20'} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.60.0: + resolution: {integrity: sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} + + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + validate-npm-package-name@7.0.2: + resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} + engines: {node: ^20.17.0 || >=22.9.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.21: + resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-spinner@1.2.0: + resolution: {integrity: sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@base-ui/react@1.5.0(@types/react@19.2.15)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.7 + '@base-ui/utils': 0.2.9(@types/react@19.2.15)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@floating-ui/utils': 0.2.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.15 + + '@base-ui/utils@0.2.9(@types/react@19.2.15)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.7 + '@floating-ui/utils': 0.2.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + reselect: 5.2.0 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.15 + + '@dotenvx/dotenvx@1.70.0': + dependencies: + commander: 11.1.0 + dotenv: 17.4.2 + eciesjs: 0.4.18 + enquirer: 2.4.1 + execa: 5.1.1 + fdir: 6.5.0(picomatch@4.0.4) + ignore: 5.3.2 + object-treeify: 1.1.33 + picomatch: 4.0.4 + which: 4.0.0 + yocto-spinner: 1.2.0 + + '@ecies/ciphers@0.2.6(@noble/ciphers@1.3.0)': + dependencies: + '@noble/ciphers': 1.3.0 + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))': + dependencies: + eslint: 9.39.4(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.2.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@floating-ui/utils@0.2.11': {} + + '@hono/node-server@1.19.14(hono@4.12.23)': + dependencies: + hono: 4.12.23 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@inquirer/ansi@2.0.7': {} + + '@inquirer/confirm@6.1.1(@types/node@20.19.41)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@20.19.41) + '@inquirer/type': 4.0.7(@types/node@20.19.41) + optionalDependencies: + '@types/node': 20.19.41 + + '@inquirer/core@11.2.1(@types/node@20.19.41)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@20.19.41) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 20.19.41 + + '@inquirer/figures@2.0.7': {} + + '@inquirer/type@4.0.7(@types/node@20.19.41)': + optionalDependencies: + '@types/node': 20.19.41 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.23) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.23 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + + '@mswjs/interceptors@0.41.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@next/env@16.2.6': {} + + '@next/eslint-plugin-next@16.2.6': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@16.2.6': + optional: true + + '@next/swc-darwin-x64@16.2.6': + optional: true + + '@next/swc-linux-arm64-gnu@16.2.6': + optional: true + + '@next/swc-linux-arm64-musl@16.2.6': + optional: true + + '@next/swc-linux-x64-gnu@16.2.6': + optional: true + + '@next/swc-linux-x64-musl@16.2.6': + optional: true + + '@next/swc-win32-arm64-msvc@16.2.6': + optional: true + + '@next/swc-win32-x64-msvc@16.2.6': + optional: true + + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/deferred-promise@3.0.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@rtsao/scc@1.1.0': {} + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.3.0': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.22.1 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.0 + + '@tailwindcss/oxide-android-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide@4.3.0': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + + '@tailwindcss/postcss@4.3.0': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + postcss: 8.5.15 + tailwindcss: 4.3.0 + + '@ts-morph/common@0.27.0': + dependencies: + fast-glob: 3.3.3 + minimatch: 10.2.5 + path-browserify: 1.0.1 + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/node@20.19.41': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.15)': + dependencies: + '@types/react': 19.2.15 + + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 20.19.41 + + '@types/statuses@2.0.6': {} + + '@types/validate-npm-package-name@4.0.2': {} + + '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/type-utils': 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 + eslint: 9.39.4(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.60.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.60.0': + dependencies: + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 + + '@typescript-eslint/tsconfig-utils@8.60.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.60.0': {} + + '@typescript-eslint/typescript-estree@8.60.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + eslint: 9.39.4(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.60.0': + dependencies: + '@typescript-eslint/types': 8.60.0 + eslint-visitor-keys: 5.0.1 + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + optional: true + + '@unrs/resolver-binding-android-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + optional: true + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + agent-base@7.1.4: {} + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + ast-types-flow@0.0.8: {} + + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.11.4: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.33: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.2 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.15: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.364 + node-releases: 2.0.46 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001793: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-width@4.1.0: {} + + client-only@0.0.1: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + code-block-writer@13.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@11.1.0: {} + + commander@14.0.3: {} + + concat-map@0.0.1: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cookie@1.1.1: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@9.0.1(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.2.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + damerau-levenshtein@1.0.8: {} + + data-uri-to-buffer@4.0.1: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dedent@1.7.2: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@3.0.0: {} + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + depd@2.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + diff@8.0.4: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dotenv@17.4.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eciesjs@0.4.18: + dependencies: + '@ecies/ciphers': 0.2.6(@noble/ciphers@1.3.0) + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.364: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.22.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.21 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.3.2: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + math-intrinsics: 1.1.0 + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.4 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-next@16.2.6(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 16.2.6 + eslint: 9.39.4(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0)) + globals: 16.4.0 + typescript-eslint: 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.10: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.2 + resolve: 2.0.0-next.7 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.7.0) + get-tsconfig: 4.14.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.17 + unrs-resolver: 1.12.2 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.4(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) + hasown: 2.0.4 + is-core-module: 2.16.2 + is-glob: 4.0.3 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.7.0)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.4 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.4(jiti@2.7.0) + hasown: 2.0.4 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@7.1.1(eslint@9.39.4(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 9.39.4(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.7.0)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.3.2 + eslint: 9.39.4(jiti@2.7.0) + estraverse: 5.3.0 + hasown: 2.0.4 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.7 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.2 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.1: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.2: {} + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-extra@11.3.5: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.4 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + fuzzysort@3.1.0: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.6.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-own-enumerable-keys@1.0.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-stream@6.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.4.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphql@16.14.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + headers-polyfill@5.0.1: + dependencies: + '@types/set-cookie-parser': 2.4.10 + set-cookie-parser: 3.1.0 + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hono@4.12.23: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + human-signals@8.0.1: {} + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.0 + + ip-address@10.2.0: {} + + ipaddr.js@1.9.1: {} + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.8.1 + + is-callable@1.2.7: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@2.0.0: {} + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-node-process@1.2.0: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-obj@3.0.0: {} + + is-plain-obj@4.1.0: {} + + is-promise@4.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-regexp@3.1.0: {} + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-stream@4.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.21 + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isexe@3.1.5: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jiti@2.7.0: {} + + jose@6.2.3: {} + + js-tokens@4.0.0: {} + + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + 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 + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@1.17.0(react@19.2.4): + dependencies: + react: 19.2.4 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-fn@2.1.0: {} + + mimic-function@5.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.15 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + msw@2.14.6(@types/node@20.19.41)(typescript@5.9.3): + dependencies: + '@inquirer/confirm': 6.1.1(@types/node@20.19.41) + '@mswjs/interceptors': 0.41.9 + '@open-draft/deferred-promise': 3.0.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.14.0 + headers-polyfill: 5.0.1 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.11.11 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.1 + type-fest: 5.7.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/node' + + mute-stream@3.0.0: {} + + nanoid@3.3.12: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + negotiator@1.0.0: {} + + next@16.2.6(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@next/env': 16.2.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 + postcss: 8.4.31 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.4) + optionalDependencies: + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-domexception@1.0.0: {} + + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-releases@2.0.46: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object-treeify@1.1.33: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + outvariant@1.4.3: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-ms@4.0.0: {} + + parseurl@1.3.3: {} + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-to-regexp@6.3.0: {} + + path-to-regexp@8.4.2: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pkce-challenge@5.0.1: {} + + possible-typed-array-names@1.1.0: {} + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.4.31: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + powershell-utils@0.1.0: {} + + prelude-ls@1.2.1: {} + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + qs@6.15.2: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react@19.2.4: {} + + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + reselect@5.2.0: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@2.0.0-next.7: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + node-exports-info: 1.6.0 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rettime@0.11.11: {} + + reusify@1.1.0: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.1: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + set-cookie-parser@3.1.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + + setprototypeof@1.2.0: {} + + shadcn@4.9.0(@types/node@20.19.41)(typescript@5.9.3): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@dotenvx/dotenvx': 1.70.0 + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@types/validate-npm-package-name': 4.0.2 + browserslist: 4.28.2 + commander: 14.0.3 + cosmiconfig: 9.0.1(typescript@5.9.3) + dedent: 1.7.2 + deepmerge: 4.3.1 + diff: 8.0.4 + execa: 9.6.1 + fast-glob: 3.3.3 + fs-extra: 11.3.5 + fuzzysort: 3.1.0 + https-proxy-agent: 7.0.6 + kleur: 4.1.5 + msw: 2.14.6(@types/node@20.19.41)(typescript@5.9.3) + node-fetch: 3.3.2 + open: 11.0.0 + ora: 8.2.0 + postcss: 8.5.15 + postcss-selector-parser: 7.1.1 + prompts: 2.4.2 + recast: 0.23.11 + stringify-object: 5.0.0 + tailwind-merge: 3.6.0 + ts-morph: 26.0.0 + tsconfig-paths: 4.2.0 + validate-npm-package-name: 7.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/node' + - babel-plugin-macros + - supports-color + - typescript + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + stable-hash@0.0.5: {} + + statuses@2.0.2: {} + + stdin-discarder@0.2.2: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + strict-event-emitter@0.5.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + stringify-object@5.0.0: + dependencies: + get-own-enumerable-keys: 1.0.0 + is-obj: 3.0.0 + is-regexp: 3.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@4.0.0: {} + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.4): + dependencies: + client-only: 0.0.1 + react: 19.2.4 + optionalDependencies: + '@babel/core': 7.29.7 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swr@2.4.1(react@19.2.4): + dependencies: + dequal: 2.0.3 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + + tagged-tag@1.0.0: {} + + tailwind-merge@3.6.0: {} + + tailwindcss@4.3.0: {} + + tapable@2.3.3: {} + + tiny-invariant@1.3.3: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tldts-core@7.4.2: {} + + tldts@7.4.2: + dependencies: + tldts-core: 7.4.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tough-cookie@6.0.1: + dependencies: + tldts: 7.4.2 + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-morph@26.0.0: + dependencies: + '@ts-morph/common': 0.27.0 + code-block-writer: 13.0.3 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + tw-animate-css@1.4.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@5.7.0: + dependencies: + tagged-tag: 1.0.0 + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unicorn-magic@0.3.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + unrs-resolver@1.12.2: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + + until-async@3.0.2: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + + util-deprecate@1.0.2: {} + + validate-npm-package-name@7.0.2: {} + + vary@1.1.2: {} + + web-streams-polyfill@3.3.3: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.21 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.21: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@4.0.0: + dependencies: + isexe: 3.1.5 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + yocto-spinner@1.2.0: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@3.25.76: {} + + zod@4.4.3: {} diff --git a/ui/pnpm-workspace.yaml b/ui/pnpm-workspace.yaml new file mode 100644 index 00000000..581a9d5b --- /dev/null +++ b/ui/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +ignoredBuiltDependencies: + - sharp + - unrs-resolver diff --git a/ui/postcss.config.mjs b/ui/postcss.config.mjs new file mode 100644 index 00000000..61e36849 --- /dev/null +++ b/ui/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/ui/public/file.svg b/ui/public/file.svg new file mode 100644 index 00000000..004145cd --- /dev/null +++ b/ui/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/public/globe.svg b/ui/public/globe.svg new file mode 100644 index 00000000..567f17b0 --- /dev/null +++ b/ui/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/public/next.svg b/ui/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/ui/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/public/vercel.svg b/ui/public/vercel.svg new file mode 100644 index 00000000..77053960 --- /dev/null +++ b/ui/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/public/window.svg b/ui/public/window.svg new file mode 100644 index 00000000..b2b2a44f --- /dev/null +++ b/ui/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/app/favicon.ico b/ui/src/app/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/ui/src/app/favicon.ico differ diff --git a/ui/src/app/globals.css b/ui/src/app/globals.css new file mode 100644 index 00000000..5a5d0426 --- /dev/null +++ b/ui/src/app/globals.css @@ -0,0 +1,151 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@import "shadcn/tailwind.css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --font-heading: var(--font-sans); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); +} + +/* ─── Light mode (fallback) ──────────────────────────────────────────────── */ +:root { + --background: oklch(0.985 0 0); + --foreground: oklch(0.18 0.01 260); + --card: oklch(1 0 0); + --card-foreground: oklch(0.18 0.01 260); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.18 0.01 260); + --primary: oklch(0.65 0.16 163); + --primary-foreground: oklch(0.23 0.09 163); + --secondary: oklch(0.96 0.015 163); + --secondary-foreground: oklch(0.28 0.06 163); + --muted: oklch(0.965 0.012 260); + --muted-foreground: oklch(0.5 0.03 260); + --accent: oklch(0.65 0.16 163); + --accent-foreground: oklch(0.23 0.09 163); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.91 0.015 260); + --input: oklch(0.91 0.015 260); + --ring: oklch(0.65 0.16 163); + --chart-1: oklch(0.65 0.16 163); + --chart-2: oklch(0.84 0.13 163); + --chart-3: oklch(0.74 0.14 82); + --chart-4: oklch(0.58 0.12 18); + --chart-5: oklch(0.48 0.04 260); + --radius: 0.375rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.18 0.01 260); + --sidebar-primary: oklch(0.65 0.16 163); + --sidebar-primary-foreground: oklch(0.23 0.09 163); + --sidebar-accent: oklch(0.94 0.02 163); + --sidebar-accent-foreground: oklch(0.18 0.01 260); + --sidebar-border: oklch(0.91 0.015 260); + --sidebar-ring: oklch(0.65 0.16 163); +} + +/* ─── Dark mode — Authsome Secure Console design system ─────────────────── */ +/* Palette: Deep Emerald (#10B981) + Obsidian (#09090B) */ +/* Ref: ui/DESIGN.md */ +.dark { + --background: oklch(0.088 0.002 277); /* #09090b obsidian */ + --foreground: oklch(0.912 0.007 338); /* #e5e1e4 on-surface */ + + --card: oklch(0.118 0.003 285); /* #131315 surface panel */ + --card-foreground: oklch(0.912 0.007 338); + + --popover: oklch(0.118 0.003 285); + --popover-foreground: oklch(0.912 0.007 338); + + /* Emerald primary */ + --primary: oklch(0.69 0.162 162); /* #10b981 */ + --primary-foreground: oklch(0.23 0.094 162); /* #003824 */ + + --secondary: oklch(0.17 0.007 308); /* #201f22 surface-high */ + --secondary-foreground: oklch(0.912 0.007 338); + + --muted: oklch(0.17 0.007 308); /* #201f22 */ + --muted-foreground: oklch(0.60 0.019 152); /* #86948a */ + + --accent: oklch(0.69 0.162 162); /* #10b981 emerald */ + --accent-foreground: oklch(0.23 0.094 162); + + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(0.21 0.005 300); /* #27272a */ + --input: oklch(0.21 0.005 300); + --ring: oklch(0.69 0.162 162); /* emerald ring */ + + --chart-1: oklch(0.69 0.162 162); + --chart-2: oklch(0.84 0.133 162); + --chart-3: oklch(0.74 0.14 82); + --chart-4: oklch(0.58 0.12 18); + --chart-5: oklch(0.40 0.02 300); + + --sidebar: oklch(0.09 0.003 285); /* #0e0e10 dim surface */ + --sidebar-foreground: oklch(0.912 0.007 338); + --sidebar-primary: oklch(0.69 0.162 162); + --sidebar-primary-foreground: oklch(0.23 0.094 162); + --sidebar-accent: oklch(0.17 0.007 308); + --sidebar-accent-foreground: oklch(0.80 0.021 153); /* #bbcabf */ + --sidebar-border: oklch(0.21 0.005 300); + --sidebar-ring: oklch(0.69 0.162 162); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + @apply font-sans; + } + a, + button, + input, + select, + textarea { + letter-spacing: 0; + } +} diff --git a/ui/src/app/layout.tsx b/ui/src/app/layout.tsx new file mode 100644 index 00000000..4167159f --- /dev/null +++ b/ui/src/app/layout.tsx @@ -0,0 +1,38 @@ +import type { Metadata } from "next"; +import { Hanken_Grotesk, JetBrains_Mono } from "next/font/google"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import "./globals.css"; + +const hankenGrotesk = Hanken_Grotesk({ + variable: "--font-sans", + subsets: ["latin"], + weight: ["400", "500", "600"], +}); + +const jetbrainsMono = JetBrains_Mono({ + variable: "--font-mono", + subsets: ["latin"], + weight: ["400", "500", "700"], +}); + +export const metadata: Metadata = { + title: "Authsome Dashboard", + description: "Local dashboard for Authsome identities, providers, and connections.", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx new file mode 100644 index 00000000..b867f5cf --- /dev/null +++ b/ui/src/app/page.tsx @@ -0,0 +1,5 @@ +import { AuthsomeDashboard } from "@/components/authsome-dashboard"; + +export default function Home() { + return ; +} diff --git a/ui/src/components/authsome-dashboard.tsx b/ui/src/components/authsome-dashboard.tsx new file mode 100644 index 00000000..355f1b35 --- /dev/null +++ b/ui/src/components/authsome-dashboard.tsx @@ -0,0 +1,799 @@ +"use client"; + +import { + AppWindow, + CheckCircle2, + CircleAlert, + ClipboardList, + Database, + ExternalLink, + GitBranch, + KeyRound, + LayoutDashboard, + Link2, + LogIn, + LogOut, + Plus, + RefreshCw, + Search, + Settings, + ShieldCheck, + UserRound, +} from "lucide-react"; +import { FormEvent, ReactNode, useMemo, useState } from "react"; +import useSWR from "swr"; + +import { ApiError, DashboardData, ProviderView, fetchDashboard } from "@/lib/authsome-api"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; + +type View = "dashboard" | "providers" | "connections" | "vault" | "audit" | "settings"; + +type NavItem = { + id: View; + label: string; + icon: ReactNode; + adminOnly?: boolean; +}; + +const NAV_ITEMS: NavItem[] = [ + { id: "dashboard", label: "Dashboard", icon: }, + { id: "providers", label: "Providers", icon: }, + { id: "connections", label: "Connections", icon: }, + { id: "vault", label: "Vault", icon: }, + { id: "audit", label: "Audit Log", icon: , adminOnly: true }, + { id: "settings", label: "Settings", icon: }, +]; + +const NEXT_URL = "/"; + +function isUnauthorized(error: unknown): boolean { + return error instanceof ApiError && error.status === 401; +} + +function StatusBadge({ status }: { status: string }) { + if (status === "connected") { + return ( + + + Connected + + ); + } + if (status === "reauth" || status === "expired" || status === "error") { + return ( + + + Re-auth + + ); + } + return Available; +} + +function AuthGate() { + return ( +
+
+
+
+
+ +
+

Authsome

+

+ Local credential access for identities, vaults, providers, and audit history. +

+
+
+
+
Local daemon
+
127.0.0.1:7998
+
+
+
Browser session
+
HttpOnly cookie
+
+
+
+ + + Open Dashboard + Use your Authsome account to continue. + + + + + + +
+
+ ); +} + +function AccountForm({ + action, + title, + submitLabel, +}: { + action: string; + title: string; + submitLabel: string; +}) { + return ( +
+ +
{title}
+ + + +
+ ); +} + +function LoadingScreen() { + return ( +
+ +
+ +
+ {Array.from({ length: 3 }).map((_, index) => ( + + ))} +
+
+
+ ); +} + +function ErrorState({ onRetry }: { onRetry: () => void }) { + return ( +
+ + + + + Dashboard Unavailable + + The daemon did not return dashboard data. + + + + + +
+ ); +} + +function Sidebar({ + activeView, + data, + onChange, +}: { + activeView: View; + data: DashboardData; + onChange: (view: View) => void; +}) { + const items = NAV_ITEMS.filter((item) => !item.adminOnly || data.account.isAdmin); + + return ( + + ); +} + +function Topbar() { + return ( +
+
+
Local Dashboard
+
Workspace Status
+
+
+ + +
+ + +
+
+
+ ); +} + +function StatCards({ data }: { data: DashboardData }) { + const stats = [ + { label: "Connected Apps", value: data.stats.connected, foot: `${data.stats.available} available`, icon: }, + { label: "Next Expiry", value: data.lastActivity, foot: "Across active providers", icon: }, + { label: "Auth Types", value: `${data.stats.oauth} / ${data.stats.apiKey}`, foot: "OAuth 2.0 / API Key", icon: }, + ]; + + return ( +
+ {stats.map((stat) => ( + + + {stat.label} + {stat.icon} + + +
{stat.value}
+

{stat.foot}

+
+
+ ))} +
+ ); +} + +function DashboardView({ data, onViewChange }: { data: DashboardData; onViewChange: (view: View) => void }) { + return ( +
+ +
+ + +
+ Connected Providers + Active credential surfaces in the current vault. +
+ +
+ + {data.connectedProviders.length ? ( +
+ {data.connectedProviders.slice(0, 6).map((provider) => ( + + ))} +
+ ) : ( + onViewChange("providers")} title="No connections yet" /> + )} +
+
+ + + Vault + Default credential namespace. + + + + + + + + + +
+
+ ); +} + +function ProviderSummary({ provider }: { provider: ProviderView }) { + return ( +
+
+
+ + {provider.logoInitial} + +
+
{provider.displayName}
+
{provider.authTypeLabel}
+
+
+ +
+

{provider.description || provider.apiUrl}

+
+ ); +} + +function EmptyBlock({ actionLabel, onAction, title }: { actionLabel: string; onAction: () => void; title: string }) { + return ( +
+
{title}
+ +
+ ); +} + +function ProvidersView({ providers }: { providers: ProviderView[] }) { + const [query, setQuery] = useState(""); + const [dialogProvider, setDialogProvider] = useState(null); + + const filteredProviders = useMemo(() => { + const normalized = query.trim().toLowerCase(); + if (!normalized) { + return providers; + } + return providers.filter((provider) => + `${provider.displayName} ${provider.name} ${provider.authTypeLabel}`.toLowerCase().includes(normalized), + ); + }, [providers, query]); + + return ( +
+ + +
+ {filteredProviders.map((provider) => ( + setDialogProvider(provider)} provider={provider} /> + ))} +
+ {!filteredProviders.length ?
No providers found.
: null} + +
+ ); +} + +function ProviderCard({ onNamedLogin, provider }: { onNamedLogin: () => void; provider: ProviderView }) { + return ( + + +
+
+ + {provider.logoInitial} + +
+ {provider.displayName} + {provider.name} +
+
+ +
+
+ +

{provider.description || provider.apiUrl}

+
+ {provider.authTypeLabel} + {provider.source} + {provider.connectionCount ? {provider.connectionCount} connections : null} +
+ {provider.requiresNamedLogin ? ( + + ) : ( +
+ + + +
+ )} +
+
+ ); +} + +function NamedConnectionDialog({ + onOpenChange, + provider, +}: { + onOpenChange: (provider: ProviderView | null) => void; + provider: ProviderView | null; +}) { + const [connectionName, setConnectionName] = useState(""); + + function handleSubmit(event: FormEvent) { + if (!connectionName.trim()) { + event.preventDefault(); + } + } + + return ( + onOpenChange(open ? provider : null)}> + + + Connection name + {provider?.displayName} already has a default connection. + +
+ + + + }>Cancel + + +
+
+
+ ); +} + +function ConnectionsView({ connections }: { connections: DashboardData["connections"] }) { + const [query, setQuery] = useState(""); + const filteredConnections = useMemo(() => { + const normalized = query.trim().toLowerCase(); + if (!normalized) { + return connections; + } + return connections.filter((row) => + `${row.connectionName} ${row.providerDisplayName} ${row.authTypeLabel}`.toLowerCase().includes(normalized), + ); + }, [connections, query]); + + return ( +
+ + + + + {filteredConnections.length ? ( + + + + Connection + Provider + Type + Status + + + + {filteredConnections.map((row) => ( + + {row.connectionName} + {row.providerDisplayName} + {row.authTypeLabel} + + + + + ))} + +
+ ) : ( +
No connections found.
+ )} +
+
+
+ ); +} + +function VaultView({ data }: { data: DashboardData }) { + return ( +
+ +
+ + + Default Vault + {data.vault.isDefault ? "Active for this account" : "Vault binding"} + + + + + + + + + Identities + Claims accepted for this account. + + + {data.identities.map((identity) => ( +
+
+ + {identity.handle} +
+ {identity.isActive ? Active : null} +
+ ))} +
+
+
+
+ ); +} + +function AuditView({ data }: { data: DashboardData }) { + return ( +
+ + + + {data.audit.events.length ? ( + + + + Time + Event + Actor + Target + Status + + + + {data.audit.events.map((event) => ( + + {event.time} + {event.event} + {event.actor} + {event.target} + {event.status} + + ))} + +
+ ) : ( +
No audit events found.
+ )} +
+
+
+ ); +} + +function SettingsView({ data }: { data: DashboardData }) { + return ( +
+ +
+ + + Account + + + + + + + + + + + + Daemon + + + + + + + + +
+
+ ); +} + + +function AdvancedSection({ children }: { children: ReactNode }) { + const [show, setShow] = useState(false); + return ( +
+ + {show &&
{children}
} +
+ ); +} + +function KeyValue({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+ + }> + {value} + + {value} + +
+ ); +} + +function SectionHeader({ description, title }: { description: string; title: string }) { + return ( +
+

{title}

+

{description}

+
+ ); +} + +function SearchInput({ + onChange, + placeholder, + value, +}: { + onChange: (value: string) => void; + placeholder: string; + value: string; +}) { + return ( + + ); +} + +function ActiveView({ + data, + onViewChange, + view, +}: { + data: DashboardData; + onViewChange: (view: View) => void; + view: View; +}) { + if (view === "providers") { + return ; + } + if (view === "connections") { + return ; + } + if (view === "vault") { + return ; + } + if (view === "audit" && data.account.isAdmin) { + return ; + } + if (view === "settings") { + return ; + } + return ; +} + +export function AuthsomeDashboard() { + const [activeView, setActiveView] = useState("dashboard"); + const { data, error, mutate } = useSWR("authsome-dashboard", fetchDashboard, { + dedupingInterval: 10_000, + revalidateOnFocus: true, + }); + + if (isUnauthorized(error)) { + return ; + } + if (error) { + return void mutate()} />; + } + if (!data) { + return ; + } + + return ( +
+
+ +
+ +
+
+
+
Authsome
+
v{data.version}
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/ui/src/components/ui/badge.tsx b/ui/src/components/ui/badge.tsx new file mode 100644 index 00000000..b20959dd --- /dev/null +++ b/ui/src/components/ui/badge.tsx @@ -0,0 +1,52 @@ +import { mergeProps } from "@base-ui/react/merge-props" +import { useRender } from "@base-ui/react/use-render" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + secondary: + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", + destructive: + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: + "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + render, + ...props +}: useRender.ComponentProps<"span"> & VariantProps) { + return useRender({ + defaultTagName: "span", + props: mergeProps<"span">( + { + className: cn(badgeVariants({ variant }), className), + }, + props + ), + render, + state: { + slot: "badge", + variant, + }, + }) +} + +export { Badge, badgeVariants } diff --git a/ui/src/components/ui/button.tsx b/ui/src/components/ui/button.tsx new file mode 100644 index 00000000..b0336017 --- /dev/null +++ b/ui/src/components/ui/button.tsx @@ -0,0 +1,58 @@ +import { Button as ButtonPrimitive } from "@base-ui/react/button" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/80", + outline: + "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + icon: "size-8", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", + "icon-sm": + "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", + "icon-lg": "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant = "default", + size = "default", + ...props +}: ButtonPrimitive.Props & VariantProps) { + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/ui/src/components/ui/card.tsx b/ui/src/components/ui/card.tsx new file mode 100644 index 00000000..40cac5f9 --- /dev/null +++ b/ui/src/components/ui/card.tsx @@ -0,0 +1,103 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ + className, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { + return ( +
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + className + )} + {...props} + /> + ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/ui/src/components/ui/dialog.tsx b/ui/src/components/ui/dialog.tsx new file mode 100644 index 00000000..014f5aa3 --- /dev/null +++ b/ui/src/components/ui/dialog.tsx @@ -0,0 +1,160 @@ +"use client" + +import * as React from "react" +import { Dialog as DialogPrimitive } from "@base-ui/react/dialog" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "lucide-react" + +function Dialog({ ...props }: DialogPrimitive.Root.Props) { + return +} + +function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) { + return +} + +function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) { + return +} + +function DialogClose({ ...props }: DialogPrimitive.Close.Props) { + return +} + +function DialogOverlay({ + className, + ...props +}: DialogPrimitive.Backdrop.Props) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: DialogPrimitive.Popup.Props & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + } + > + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean +}) { + return ( +
+ {children} + {showCloseButton && ( + }> + Close + + )} +
+ ) +} + +function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: DialogPrimitive.Description.Props) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/ui/src/components/ui/input.tsx b/ui/src/components/ui/input.tsx new file mode 100644 index 00000000..7d21babb --- /dev/null +++ b/ui/src/components/ui/input.tsx @@ -0,0 +1,20 @@ +import * as React from "react" +import { Input as InputPrimitive } from "@base-ui/react/input" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/ui/src/components/ui/scroll-area.tsx b/ui/src/components/ui/scroll-area.tsx new file mode 100644 index 00000000..84c1e9fb --- /dev/null +++ b/ui/src/components/ui/scroll-area.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area" + +import { cn } from "@/lib/utils" + +function ScrollArea({ + className, + children, + ...props +}: ScrollAreaPrimitive.Root.Props) { + return ( + + + {children} + + + + + ) +} + +function ScrollBar({ + className, + orientation = "vertical", + ...props +}: ScrollAreaPrimitive.Scrollbar.Props) { + return ( + + + + ) +} + +export { ScrollArea, ScrollBar } diff --git a/ui/src/components/ui/separator.tsx b/ui/src/components/ui/separator.tsx new file mode 100644 index 00000000..6e1369e4 --- /dev/null +++ b/ui/src/components/ui/separator.tsx @@ -0,0 +1,25 @@ +"use client" + +import { Separator as SeparatorPrimitive } from "@base-ui/react/separator" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + ...props +}: SeparatorPrimitive.Props) { + return ( + + ) +} + +export { Separator } diff --git a/ui/src/components/ui/skeleton.tsx b/ui/src/components/ui/skeleton.tsx new file mode 100644 index 00000000..0118624f --- /dev/null +++ b/ui/src/components/ui/skeleton.tsx @@ -0,0 +1,13 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/ui/src/components/ui/table.tsx b/ui/src/components/ui/table.tsx new file mode 100644 index 00000000..abeaced4 --- /dev/null +++ b/ui/src/components/ui/table.tsx @@ -0,0 +1,116 @@ +"use client" + +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Table({ className, ...props }: React.ComponentProps<"table">) { + return ( +
+ + + ) +} + +function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { + return ( + + ) +} + +function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { + return ( + + ) +} + +function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { + return ( + tr]:last:border-b-0", + className + )} + {...props} + /> + ) +} + +function TableRow({ className, ...props }: React.ComponentProps<"tr">) { + return ( + + ) +} + +function TableHead({ className, ...props }: React.ComponentProps<"th">) { + return ( +
+ ) +} + +function TableCell({ className, ...props }: React.ComponentProps<"td">) { + return ( + + ) +} + +function TableCaption({ + className, + ...props +}: React.ComponentProps<"caption">) { + return ( +
+ ) +} + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/ui/src/components/ui/tabs.tsx b/ui/src/components/ui/tabs.tsx new file mode 100644 index 00000000..8ee8054f --- /dev/null +++ b/ui/src/components/ui/tabs.tsx @@ -0,0 +1,82 @@ +"use client" + +import { Tabs as TabsPrimitive } from "@base-ui/react/tabs" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + orientation = "horizontal", + ...props +}: TabsPrimitive.Root.Props) { + return ( + + ) +} + +const tabsListVariants = cva( + "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none", + { + variants: { + variant: { + default: "bg-muted", + line: "gap-1 bg-transparent", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function TabsList({ + className, + variant = "default", + ...props +}: TabsPrimitive.List.Props & VariantProps) { + return ( + + ) +} + +function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) { + return ( + + ) +} + +function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants } diff --git a/ui/src/components/ui/tooltip.tsx b/ui/src/components/ui/tooltip.tsx new file mode 100644 index 00000000..69e8a822 --- /dev/null +++ b/ui/src/components/ui/tooltip.tsx @@ -0,0 +1,66 @@ +"use client" + +import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip" + +import { cn } from "@/lib/utils" + +function TooltipProvider({ + delay = 0, + ...props +}: TooltipPrimitive.Provider.Props) { + return ( + + ) +} + +function Tooltip({ ...props }: TooltipPrimitive.Root.Props) { + return +} + +function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) { + return +} + +function TooltipContent({ + className, + side = "top", + sideOffset = 4, + align = "center", + alignOffset = 0, + children, + ...props +}: TooltipPrimitive.Popup.Props & + Pick< + TooltipPrimitive.Positioner.Props, + "align" | "alignOffset" | "side" | "sideOffset" + >) { + return ( + + + + {children} + + + + + ) +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/ui/src/lib/authsome-api.ts b/ui/src/lib/authsome-api.ts new file mode 100644 index 00000000..7042189f --- /dev/null +++ b/ui/src/lib/authsome-api.ts @@ -0,0 +1,329 @@ +export type DashboardStats = { + connected: number; + available: number; + oauth: number; + apiKey: number; +}; + +export type ProviderView = { + name: string; + displayName: string; + authType: "oauth2" | "api_key" | string; + authTypeLabel: string; + apiUrl: string; + description: string; + source: "bundled" | "custom" | string; + logoInitial: string; + status: "available" | "connected" | "reauth" | string; + scopeCount: number; + connectionCount: number; + requiresNamedLogin: boolean; +}; + +export type ConnectionRow = { + providerName: string; + providerDisplayName: string; + connectionName: string; + status: string; + authTypeLabel: string; +}; + +export type IdentityRow = { + handle: string; + isActive: boolean; +}; + +export type AuditRow = { + eventId: string; + time: string; + event: string; + source: string; + actor: string; + target: string; + status: string; + metadata: Record; +}; + +export type DashboardData = { + version: string; + account: { + email: string | null; + roleLabel: string | null; + isAdmin: boolean; + principalId: string | null; + identity: string | null; + }; + stats: DashboardStats; + lastActivity: string; + providers: ProviderView[]; + connectedProviders: ProviderView[]; + connections: ConnectionRow[]; + identities: IdentityRow[]; + vault: { + vaultId: string | null; + handle: string; + isDefault: boolean; + }; + audit: { + canView: boolean; + total: number; + events: AuditRow[]; + }; +}; + +type WhoamiResponse = { + version: string; + identity?: string; + active_identity?: string; + principal_id?: string; + principal_role?: string; + account_email?: string; + vault_id?: string; +}; + +type ConnectionSummary = { + connection_name: string; + auth_type?: string; + status?: string; + scopes?: string[]; + expires_at?: string | null; +}; + +type ProviderResponse = { + name: string; + display_name?: string; + auth_type?: string; + api_url?: string | string[] | null; + oauth?: { + base_url?: string | null; + } | null; + metadata?: { + description?: string; + }; +}; + +type ConnectionsResponse = { + connections: Array<{ + name: string; + connections: ConnectionSummary[]; + }>; + by_source: Record; +}; + +type AuditResponse = { + entries: Array>; +}; + +export class ApiError extends Error { + status: number; + + constructor(status: number, message: string) { + super(message); + this.status = status; + } +} + +async function requestJson(path: string): Promise { + const response = await fetch(path, { + credentials: "same-origin", + headers: { + Accept: "application/json", + }, + }); + + if (!response.ok) { + let message = response.statusText || "Request failed"; + try { + const payload = (await response.json()) as { detail?: string; message?: string }; + message = payload.detail || payload.message || message; + } catch { + // Status is sufficient for the UI's failure modes. + } + throw new ApiError(response.status, message); + } + + return response.json() as Promise; +} + +function authTypeLabel(authType?: string): string { + return authType === "oauth2" ? "OAuth 2.0" : authType === "api_key" ? "API Key" : authType || "Provider"; +} + +function providerApiUrl(provider: ProviderResponse): string { + if (Array.isArray(provider.api_url)) { + return provider.api_url.filter(Boolean).join(", ") || provider.name; + } + return provider.api_url || provider.oauth?.base_url || provider.name; +} + +function providerStatus(connections: ConnectionSummary[]): ProviderView["status"] { + if (!connections.length) { + return "available"; + } + return connections.some((connection) => ["error", "expired"].includes(connection.status || "")) + ? "reauth" + : "connected"; +} + +function providerView( + provider: ProviderResponse, + source: string, + connections: ConnectionSummary[], +): ProviderView { + const displayName = provider.display_name || provider.name; + return { + name: provider.name, + displayName, + authType: provider.auth_type || "provider", + authTypeLabel: authTypeLabel(provider.auth_type), + apiUrl: providerApiUrl(provider), + description: provider.metadata?.description || "", + source, + logoInitial: (displayName[0] || "?").toUpperCase(), + status: providerStatus(connections), + scopeCount: connections[0]?.scopes?.length || 0, + connectionCount: connections.length, + requiresNamedLogin: connections.some((connection) => connection.connection_name === "default"), + }; +} + +function buildProviders(data: ConnectionsResponse): ProviderView[] { + const connectionMap = new Map(data.connections.map((group) => [group.name, group.connections])); + return Object.entries(data.by_source).flatMap(([source, providers]) => + providers.map((provider) => providerView(provider, source, connectionMap.get(provider.name) || [])), + ); +} + +function buildConnectionRows(data: ConnectionsResponse, providers: ProviderView[]): ConnectionRow[] { + const providerMap = new Map(providers.map((provider) => [provider.name, provider])); + return data.connections + .flatMap((group) => { + const provider = providerMap.get(group.name); + return group.connections.map((connection) => ({ + providerName: group.name, + providerDisplayName: provider?.displayName || group.name, + connectionName: connection.connection_name, + status: connection.status || "unknown", + authTypeLabel: authTypeLabel(connection.auth_type || provider?.authType), + })); + }) + .sort((a, b) => `${a.providerDisplayName}:${a.connectionName}`.localeCompare(`${b.providerDisplayName}:${b.connectionName}`)); +} + +function formatRelative(value: string | null | undefined): string | null { + if (!value) { + return null; + } + const parsed = new Date(value); + if (Number.isNaN(parsed.valueOf())) { + return null; + } + const deltaSeconds = Math.round((parsed.valueOf() - Date.now()) / 1000); + const absSeconds = Math.abs(deltaSeconds); + const direction = deltaSeconds >= 0 ? "in" : "ago"; + const units: Array<[number, string]> = [ + [86_400, "day"], + [3_600, "hour"], + [60, "minute"], + [1, "second"], + ]; + const [unitSeconds, unit] = units.find(([seconds]) => absSeconds >= seconds) || [1, "second"]; + const amount = Math.max(1, Math.floor(absSeconds / unitSeconds)); + const label = `${amount} ${unit}${amount === 1 ? "" : "s"}`; + return direction === "in" ? `in ${label}` : `${label} ago`; +} + +function lastActivity(data: ConnectionsResponse): string { + const latest = data.connections + .flatMap((group) => group.connections) + .map((connection) => connection.expires_at) + .filter((value): value is string => Boolean(value)) + .sort((a, b) => new Date(b).valueOf() - new Date(a).valueOf())[0]; + return formatRelative(latest) || "-"; +} + +function humanize(value: unknown): string { + const event = String(value || "audit_event").replaceAll("_", " ").replaceAll("-", " ").trim(); + return event ? event[0].toUpperCase() + event.slice(1) : "Audit event"; +} + +function formatAuditTime(value: unknown): string { + if (!value) { + return "-"; + } + const parsed = new Date(String(value)); + if (Number.isNaN(parsed.valueOf())) { + return String(value); + } + return parsed.toISOString().replace("T", " ").slice(0, 16) + " UTC"; +} + +function buildAuditRows(entries: AuditResponse["entries"]): AuditRow[] { + const known = new Set(["event_id", "timestamp", "event", "source", "principal_id", "identity", "provider", "connection", "status"]); + return entries.map((entry, index) => { + const provider = entry.provider ? String(entry.provider) : ""; + const connection = entry.connection ? String(entry.connection) : ""; + const metadata = Object.fromEntries(Object.entries(entry).filter(([key, value]) => !known.has(key) && value != null)); + return { + eventId: String(entry.event_id || `${entry.timestamp || "event"}-${index}`), + time: formatAuditTime(entry.timestamp), + event: humanize(entry.event), + source: String(entry.source || "internal"), + actor: String(entry.identity || entry.principal_id || "system"), + target: [provider, connection].filter(Boolean).join(" / ") || "Authsome", + status: String(entry.status || "-"), + metadata, + }; + }); +} + +function roleLabel(role: string | undefined): string | null { + if (!role) { + return null; + } + return role.slice(0, 1).toUpperCase() + role.slice(1); +} + +export async function fetchDashboard(): Promise { + const [whoami, connectionsData] = await Promise.all([ + requestJson("/whoami"), + requestJson("/connections"), + ]); + const isAdmin = whoami.principal_role === "admin"; + const audit = isAdmin ? await requestJson("/audit/events?limit=100") : { entries: [] }; + const providers = buildProviders(connectionsData); + const connections = buildConnectionRows(connectionsData, providers); + const connectedProviders = providers.filter((provider) => provider.status !== "available"); + + return { + version: whoami.version, + account: { + email: whoami.account_email || null, + roleLabel: roleLabel(whoami.principal_role), + isAdmin, + principalId: whoami.principal_id || null, + identity: whoami.identity || whoami.active_identity || null, + }, + stats: { + connected: connectedProviders.length, + available: providers.length - connectedProviders.length, + oauth: connectedProviders.filter((provider) => provider.authType === "oauth2").length, + apiKey: connectedProviders.filter((provider) => provider.authType === "api_key").length, + }, + lastActivity: lastActivity(connectionsData), + providers, + connectedProviders: connectedProviders.slice(0, 6), + connections, + identities: whoami.identity ? [{ handle: whoami.identity, isActive: true }] : [], + vault: { + vaultId: whoami.vault_id || null, + handle: "default", + isDefault: true, + }, + audit: { + canView: isAdmin, + total: audit.entries.length, + events: buildAuditRows(audit.entries), + }, + }; +} diff --git a/ui/src/lib/utils.ts b/ui/src/lib/utils.ts new file mode 100644 index 00000000..bd0c391d --- /dev/null +++ b/ui/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 00000000..cf9c65d3 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +} diff --git a/uv.lock b/uv.lock index e78d981f..d9009718 100644 --- a/uv.lock +++ b/uv.lock @@ -173,7 +173,6 @@ dependencies = [ { name = "click" }, { name = "cryptography" }, { name = "fastapi" }, - { name = "jinja2" }, { name = "keyring" }, { name = "loguru" }, { name = "mitmproxy" }, @@ -211,7 +210,6 @@ requires-dist = [ { name = "cryptography", specifier = ">=41.0" }, { name = "fastapi", specifier = ">=0.115" }, { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.28.1" }, - { name = "jinja2", specifier = ">=3.1" }, { name = "keyring", specifier = ">=24.0" }, { name = "loguru", specifier = ">=0.7" }, { name = "mitmproxy", specifier = ">=11.0" },