diff --git a/services/hackbot-ui/.dockerignore b/services/hackbot-ui/.dockerignore new file mode 100644 index 0000000000..0dcfe3e9f0 --- /dev/null +++ b/services/hackbot-ui/.dockerignore @@ -0,0 +1,10 @@ +node_modules +.next +.git +.env +.env.local +*.db +*.db-shm +*.db-wal +npm-debug.log +README.md diff --git a/services/hackbot-ui/.env.example b/services/hackbot-ui/.env.example new file mode 100644 index 0000000000..560468e4e7 --- /dev/null +++ b/services/hackbot-ui/.env.example @@ -0,0 +1,17 @@ +# Base URL of the hackbot-api service (no trailing slash). +HACKBOT_API_URL=http://localhost:8080 + +# API key sent as the X-API-Key header. Must match hackbot-api's +# EXTERNAL_API_KEY. Stays server-side — it is never shipped to the browser. +HACKBOT_API_KEY= + +# Public base URL of THIS web app, used by better-auth for callbacks. +BETTER_AUTH_URL=http://localhost:3000 + +# Random 32+ char secret used to sign sessions. Generate with `openssl rand -base64 32`. +BETTER_AUTH_SECRET= + +# Google OAuth client (console.cloud.google.com → APIs & Services → Credentials). +# Authorized redirect URI must be: /api/auth/callback/google +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= diff --git a/services/hackbot-ui/.gitignore b/services/hackbot-ui/.gitignore new file mode 100644 index 0000000000..a7b8307af5 --- /dev/null +++ b/services/hackbot-ui/.gitignore @@ -0,0 +1,12 @@ +# The repo-root .gitignore ignores `lib/` (Python build artifacts). Re-include +# this Next.js app's lib/ source directory. +!/lib/ +/node_modules +/.next +/out +/build +.env +.env.local +*.tsbuildinfo +next-env.d.ts.bak +.DS_Store diff --git a/services/hackbot-ui/Dockerfile b/services/hackbot-ui/Dockerfile new file mode 100644 index 0000000000..f6eefc120a --- /dev/null +++ b/services/hackbot-ui/Dockerfile @@ -0,0 +1,34 @@ +# syntax=docker/dockerfile:1 + +FROM node:22-slim AS deps +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci --no-audit --no-fund + +FROM node:22-slim AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +FROM node:22-slim AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +# Cloud Run injects its own PORT (8080) which the standalone server honors. +# Bind to all interfaces — the standalone server defaults HOSTNAME to +# "localhost", which would fail Cloud Run's health check. +ENV HOSTNAME=0.0.0.0 + +RUN useradd --create-home --shell /bin/bash app + +# Next.js standalone output bundles only the files needed to run the server. +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static + +USER app +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/services/hackbot-ui/README.md b/services/hackbot-ui/README.md new file mode 100644 index 0000000000..c14c2d4828 --- /dev/null +++ b/services/hackbot-ui/README.md @@ -0,0 +1,138 @@ +# Hackbot Launchpad + +A small Next.js web UI for **demonstrating** the [`hackbot-api`](../hackbot-api). +It lets you: + +- **Trigger** the `bug-fix` agent by entering a Bugzilla bug ID (plus optional + model / max-turns / effort). +- **Observe state** — the dashboard and the run detail page poll the API and + show each run's status live (`pending → running → succeeded / failed / timed_out`). +- **Read the result** on completion — the agent's summary findings (rendered as + an "Agent log" pane when a free-text log/output field is present) and the + artifacts written to the results bucket, each a **download link** (the browser + is redirected to a short-lived signed GCS URL). + +> This is a demo surface, not a system of record. It has no list-runs endpoint +> upstream, so the browser remembers the runs you triggered in `localStorage`. + +## Architecture + +``` +Browser ──> Next.js route handlers (/api/*) ──> hackbot-api (X-API-Key) + └─ better-auth (Google, @mozilla.com only) +``` + +The `hackbot-api` key never reaches the browser: every call goes through a +server-side route handler in `app/api/*` that injects the `X-API-Key` header +(see `lib/hackbot.ts`). Those handlers also re-validate the session. + +### Authentication + +Sign-in is **Google OAuth via [better-auth](https://better-auth.com)**, limited +to `@mozilla.com` accounts: + +- **No database — fully stateless.** `lib/auth.ts` configures better-auth with + no `database`, so the session lives entirely in a signed + encrypted (JWE) + cookie and the server never queries any store to validate it. The only shared + state is `BETTER_AUTH_SECRET`. See better-auth's + [stateless session docs](https://better-auth.com/docs/concepts/session-management#basic-stateless-setup). +- The `@mozilla.com` restriction is enforced in two mode-independent layers: + the Google provider's `mapProfileToUser` rejects non-Mozilla identities during + the OAuth callback (before a session is issued), and `getAuthedEmail()` + (`lib/session.ts`) re-checks the domain on every proxy request. `hd: "mozilla.com"` + is also passed to Google as a UI hint. +- `middleware.ts` redirects unauthenticated visitors to `/login` (and returns + `401` JSON for `/api/*`). + +## Endpoints used (hackbot-api) + +| UI action | hackbot-api call | +| ----------------- | --------------------------------------- | +| Trigger a run | `POST /agents/bug-fix/runs` | +| Poll run state | `GET /runs/{run_id}` | +| Download artifact | `GET /runs/{run_id}/artifacts/{path}` † | +| (available) | `GET /agents` | + +## Local development + +1. Install dependencies: + + ```bash + npm install + ``` + +2. Configure the environment: + + ```bash + cp .env.example .env.local + # fill in HACKBOT_API_URL, HACKBOT_API_KEY, + # BETTER_AUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET + ``` + + For the Google OAuth client, add this authorized redirect URI: + `http://localhost:3000/api/auth/callback/google` + + (No database setup or migration step — auth is stateless.) + +3. Run it: + + ```bash + npm run dev + ``` + + Open http://localhost:3000 — you'll be redirected to `/login`. + +## Production build / container + +```bash +docker build -t hackbot-ui -f services/hackbot-ui/Dockerfile services/hackbot-ui +docker run -p 3000:3000 --env-file services/hackbot-ui/.env.local hackbot-ui +``` + +The image uses Next.js `output: "standalone"`. + +### Cloud Run + +Stateless auth makes this Cloud Run-friendly out of the box: sessions are +self-contained cookies, so they survive scale-to-zero and work across any number +of instances — **as long as every instance shares the same `BETTER_AUTH_SECRET`** +(set it as a secret/env var on the service). No database to provision. + +Use `./deploy.sh` (build → push to Artifact Registry → `gcloud run deploy`). It +keeps secrets in Secret Manager and reads everything else from env vars. The +service runs as a **dedicated least-privilege service account** +(`hackbot-ui-run@`) that the script creates and grants +`secretmanager.secretAccessor` on just the three secrets it reads — no other GCP +permissions (it reaches hackbot-api over HTTPS with the API key, not via IAM). + +The hackbot-api key reuses the existing shared **`external-api-key`** secret +(override with `API_KEY_SECRET=...`), so only two UI-specific secrets need +creating: `hackbot-ui-auth-secret` and `hackbot-ui-google-secret`. + +```bash +# one-time: enable APIs + create the two UI secrets (see the header of deploy.sh). +# The deployer needs run.admin + iam.serviceAccountUser on the new SA. + +# first deploy (no BETTER_AUTH_URL yet — Cloud Run assigns the URL): +PROJECT=my-proj REGION=us-central1 \ +HACKBOT_API_URL=https://hackbot-api-xxxx.run.app \ +GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.com \ +./deploy.sh + +# then: add /api/auth/callback/google to the Google OAuth client, +# and re-run with BETTER_AUTH_URL= to finalize. +``` + +`HACKBOT_API_URL` is the hackbot-api's public Cloud Run URL; `HACKBOT_API_KEY` +must match its `X-API-Key`. + +## Environment variables + +| Variable | Purpose | +| ---------------------- | -------------------------------------------------- | +| `HACKBOT_API_URL` | Base URL of hackbot-api (no trailing slash) | +| `HACKBOT_API_KEY` | Value for the `X-API-Key` header (server-side) | +| `BETTER_AUTH_URL` | Public base URL of this app | +| `BETTER_AUTH_SECRET` | Session signing secret (`openssl rand -base64 32`) | +| `GOOGLE_CLIENT_ID` | Google OAuth client ID | +| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | diff --git a/services/hackbot-ui/app/api/agents/route.ts b/services/hackbot-ui/app/api/agents/route.ts new file mode 100644 index 0000000000..ed5fbcf695 --- /dev/null +++ b/services/hackbot-ui/app/api/agents/route.ts @@ -0,0 +1,19 @@ +import { NextResponse } from "next/server"; + +import { HackbotError, listAgents } from "@/lib/hackbot"; +import { getAuthedEmail } from "@/lib/session"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + if (!(await getAuthedEmail())) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + try { + const agents = await listAgents(); + return NextResponse.json(agents); + } catch (err) { + const status = err instanceof HackbotError ? err.status : 500; + return NextResponse.json({ error: (err as Error).message }, { status }); + } +} diff --git a/services/hackbot-ui/app/api/auth/[...all]/route.ts b/services/hackbot-ui/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000000..076e578619 --- /dev/null +++ b/services/hackbot-ui/app/api/auth/[...all]/route.ts @@ -0,0 +1,5 @@ +import { toNextJsHandler } from "better-auth/next-js"; + +import { auth } from "@/lib/auth"; + +export const { GET, POST } = toNextJsHandler(auth); diff --git a/services/hackbot-ui/app/api/runs/[runId]/artifacts/[...path]/route.ts b/services/hackbot-ui/app/api/runs/[runId]/artifacts/[...path]/route.ts new file mode 100644 index 0000000000..0a084cdb84 --- /dev/null +++ b/services/hackbot-ui/app/api/runs/[runId]/artifacts/[...path]/route.ts @@ -0,0 +1,30 @@ +import { NextResponse } from "next/server"; + +import { getArtifactDownloadUrl, HackbotError } from "@/lib/hackbot"; +import { getAuthedEmail } from "@/lib/session"; + +export const dynamic = "force-dynamic"; + +// GET /api/runs/:runId/artifacts/*path +// Resolves a signed download URL from hackbot-api and redirects the browser +// straight to GCS, so artifact bytes never stream through this server and the +// X-API-Key stays server-side. +export async function GET( + _req: Request, + { params }: { params: Promise<{ runId: string; path: string[] }> } +) { + if (!(await getAuthedEmail())) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { runId, path } = await params; + const artifactName = path.join("/"); // catch-all segments are pre-decoded + + try { + const { url } = await getArtifactDownloadUrl(runId, artifactName); + return NextResponse.redirect(url, 302); + } catch (err) { + const status = err instanceof HackbotError ? err.status : 500; + return NextResponse.json({ error: (err as Error).message }, { status }); + } +} diff --git a/services/hackbot-ui/app/api/runs/[runId]/route.ts b/services/hackbot-ui/app/api/runs/[runId]/route.ts new file mode 100644 index 0000000000..d8945ac1f1 --- /dev/null +++ b/services/hackbot-ui/app/api/runs/[runId]/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from "next/server"; + +import { getRun, HackbotError } from "@/lib/hackbot"; +import { getAuthedEmail } from "@/lib/session"; + +export const dynamic = "force-dynamic"; + +// GET /api/runs/:runId — proxy the full run document (state, summary, artifacts). +export async function GET( + _req: Request, + { params }: { params: Promise<{ runId: string }> } +) { + if (!(await getAuthedEmail())) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { runId } = await params; + try { + const run = await getRun(runId); + return NextResponse.json(run); + } catch (err) { + const status = err instanceof HackbotError ? err.status : 500; + return NextResponse.json({ error: (err as Error).message }, { status }); + } +} diff --git a/services/hackbot-ui/app/api/runs/route.ts b/services/hackbot-ui/app/api/runs/route.ts new file mode 100644 index 0000000000..6384220d17 --- /dev/null +++ b/services/hackbot-ui/app/api/runs/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from "next/server"; + +import { createRun, HackbotError } from "@/lib/hackbot"; +import { getAuthedEmail } from "@/lib/session"; + +export const dynamic = "force-dynamic"; + +// POST /api/runs { agent: string, inputs: object } +// Triggers a new agent run via hackbot-api. +export async function POST(req: NextRequest) { + if (!(await getAuthedEmail())) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + let body: unknown; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); + } + + const { agent, inputs } = (body ?? {}) as { + agent?: string; + inputs?: Record; + }; + + if (!agent || typeof agent !== "string") { + return NextResponse.json( + { error: "Missing 'agent' field" }, + { status: 400 } + ); + } + + try { + const run = await createRun(agent, inputs ?? {}); + return NextResponse.json(run, { status: 201 }); + } catch (err) { + const status = err instanceof HackbotError ? err.status : 500; + return NextResponse.json({ error: (err as Error).message }, { status }); + } +} diff --git a/services/hackbot-ui/app/globals.css b/services/hackbot-ui/app/globals.css new file mode 100644 index 0000000000..1dcd6d5fc6 --- /dev/null +++ b/services/hackbot-ui/app/globals.css @@ -0,0 +1,285 @@ +:root { + --bg: #0f1117; + --panel: #171a23; + --panel-2: #1e2230; + --border: #2a2f3d; + --text: #e6e9ef; + --muted: #9aa3b2; + --accent: #5b8cff; + --accent-hover: #4a7af0; + --green: #2ea043; + --amber: #d2992a; + --red: #e5534b; + --blue: #4a7af0; + --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + background: var(--bg); + color: var(--text); + font-family: + system-ui, + -apple-system, + "Segoe UI", + Roboto, + sans-serif; + font-size: 15px; + line-height: 1.5; +} + +a { + color: var(--accent); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +.container { + max-width: 960px; + margin: 0 auto; + padding: 0 24px 64px; +} + +header.topbar { + border-bottom: 1px solid var(--border); + background: var(--panel); + margin-bottom: 32px; +} +header.topbar .inner { + max-width: 960px; + margin: 0 auto; + padding: 16px 24px; + display: flex; + align-items: baseline; + gap: 12px; +} +header.topbar h1 { + font-size: 18px; + margin: 0; +} +header.topbar .tag { + font-size: 12px; + color: var(--muted); +} + +.panel { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 10px; + padding: 20px; + margin-bottom: 24px; +} +.panel h2 { + margin: 0 0 16px; + font-size: 15px; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--muted); +} + +label { + display: block; + font-size: 13px; + color: var(--muted); + margin-bottom: 6px; +} + +input, +select, +textarea { + width: 100%; + background: var(--panel-2); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--text); + padding: 9px 11px; + font-size: 14px; + font-family: inherit; +} +input:focus, +select:focus, +textarea:focus { + outline: none; + border-color: var(--accent); +} + +.field { + margin-bottom: 16px; +} +.row { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 16px; +} + +button { + background: var(--accent); + color: white; + border: none; + border-radius: 6px; + padding: 10px 18px; + font-size: 14px; + font-weight: 600; + cursor: pointer; +} +button:hover { + background: var(--accent-hover); +} +button:disabled { + opacity: 0.55; + cursor: not-allowed; +} +button.secondary { + background: transparent; + border: 1px solid var(--border); + color: var(--muted); +} + +.badge { + display: inline-block; + padding: 2px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 600; + text-transform: capitalize; +} +.badge.pending { + background: rgba(210, 153, 42, 0.16); + color: var(--amber); +} +.badge.running { + background: rgba(74, 122, 240, 0.18); + color: var(--blue); +} +.badge.succeeded { + background: rgba(46, 160, 67, 0.18); + color: var(--green); +} +.badge.failed, +.badge.timed_out { + background: rgba(229, 83, 75, 0.18); + color: var(--red); +} + +table.runs { + width: 100%; + border-collapse: collapse; +} +table.runs th, +table.runs td { + text-align: left; + padding: 10px 8px; + border-bottom: 1px solid var(--border); + font-size: 14px; +} +table.runs th { + color: var(--muted); + font-weight: 500; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.04em; +} +table.runs td.mono { + font-family: var(--mono); + font-size: 13px; +} + +.muted { + color: var(--muted); +} +.error-banner { + background: rgba(229, 83, 75, 0.12); + border: 1px solid rgba(229, 83, 75, 0.4); + color: #f3b0ac; + border-radius: 6px; + padding: 12px 14px; + margin-bottom: 16px; + font-size: 14px; +} + +pre.log { + background: #0a0c12; + border: 1px solid var(--border); + border-radius: 8px; + padding: 16px; + overflow: auto; + font-family: var(--mono); + font-size: 13px; + line-height: 1.55; + max-height: 520px; + white-space: pre-wrap; + word-break: break-word; + margin: 0; +} + +dl.kv { + display: grid; + grid-template-columns: 160px 1fr; + gap: 8px 16px; + margin: 0; +} +dl.kv dt { + color: var(--muted); + font-size: 13px; +} +dl.kv dd { + margin: 0; + font-family: var(--mono); + font-size: 13px; + word-break: break-word; +} + +.artifact-list { + list-style: none; + padding: 0; + margin: 0; +} +.artifact-list li { + display: flex; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px solid var(--border); + font-family: var(--mono); + font-size: 13px; +} +.artifact-list li:last-child { + border-bottom: none; +} + +.toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} +.spinner-note { + font-size: 13px; + color: var(--muted); + display: inline-flex; + align-items: center; + gap: 8px; +} +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--blue); + animation: pulse 1.2s ease-in-out infinite; +} +@keyframes pulse { + 0%, + 100% { + opacity: 0.3; + } + 50% { + opacity: 1; + } +} diff --git a/services/hackbot-ui/app/layout.tsx b/services/hackbot-ui/app/layout.tsx new file mode 100644 index 0000000000..7ba906be15 --- /dev/null +++ b/services/hackbot-ui/app/layout.tsx @@ -0,0 +1,32 @@ +import type { Metadata } from "next"; + +import "./globals.css"; +import { UserMenu } from "@/components/UserMenu"; + +export const metadata: Metadata = { + title: "Hackbot Launchpad", + description: "Launch and observe hackbot agents", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +
+
+

🚀 Hackbot Launchpad

+ demo +
+ +
+
+
+
{children}
+ + + ); +} diff --git a/services/hackbot-ui/app/login/page.tsx b/services/hackbot-ui/app/login/page.tsx new file mode 100644 index 0000000000..1a526a5a14 --- /dev/null +++ b/services/hackbot-ui/app/login/page.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useState } from "react"; + +import { signIn } from "@/lib/auth-client"; + +export default function LoginPage() { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function onGoogle() { + setError(null); + setLoading(true); + try { + await signIn.social({ + provider: "google", + callbackURL: "/", + errorCallbackURL: "/login?error=denied", + }); + } catch (err) { + setError((err as Error).message); + setLoading(false); + } + } + + const denied = + typeof window !== "undefined" && + new URLSearchParams(window.location.search).get("error"); + + return ( +
+
+

Sign in

+

+ Hackbot Launchpad is restricted to @mozilla.com{" "} + Google accounts. +

+ {denied && ( +
+ Sign-in was denied. Use your @mozilla.com account. +
+ )} + {error &&
{error}
} + +
+
+ ); +} diff --git a/services/hackbot-ui/app/page.tsx b/services/hackbot-ui/app/page.tsx new file mode 100644 index 0000000000..bd79a58d3d --- /dev/null +++ b/services/hackbot-ui/app/page.tsx @@ -0,0 +1,18 @@ +import { RecentRuns } from "@/components/RecentRuns"; +import { TriggerForm } from "@/components/TriggerForm"; + +export default function HomePage() { + return ( + <> +
+

Trigger a run

+ +
+ +
+

Recent runs

+ +
+ + ); +} diff --git a/services/hackbot-ui/app/runs/[runId]/page.tsx b/services/hackbot-ui/app/runs/[runId]/page.tsx new file mode 100644 index 0000000000..3fe6e24205 --- /dev/null +++ b/services/hackbot-ui/app/runs/[runId]/page.tsx @@ -0,0 +1,10 @@ +import { RunDetail } from "@/components/RunDetail"; + +export default async function RunPage({ + params, +}: { + params: Promise<{ runId: string }>; +}) { + const { runId } = await params; + return ; +} diff --git a/services/hackbot-ui/components/RecentRuns.tsx b/services/hackbot-ui/components/RecentRuns.tsx new file mode 100644 index 0000000000..8de6b753d3 --- /dev/null +++ b/services/hackbot-ui/components/RecentRuns.tsx @@ -0,0 +1,86 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +import { isTerminal } from "@/lib/types"; +import { loadRuns, type TrackedRun, updateRunStatus } from "@/lib/store"; +import { StatusBadge } from "./StatusBadge"; + +// Polls the status of any non-terminal tracked runs so the dashboard stays live. +const POLL_MS = 5000; + +export function RecentRuns() { + const [runs, setRuns] = useState(null); + + useEffect(() => { + setRuns(loadRuns()); + }, []); + + useEffect(() => { + if (!runs) return; + const active = runs.filter((r) => !isTerminal(r.status)); + if (active.length === 0) return; + + const timer = setInterval(async () => { + let changed = false; + for (const r of active) { + try { + const res = await fetch(`/api/runs/${r.run_id}`); + if (!res.ok) continue; + const doc = await res.json(); + if (doc.status && doc.status !== r.status) { + updateRunStatus(r.run_id, doc.status); + changed = true; + } + } catch { + // transient; try again next tick + } + } + if (changed) setRuns(loadRuns()); + }, POLL_MS); + + return () => clearInterval(timer); + }, [runs]); + + if (runs === null) { + return

Loading…

; + } + + if (runs.length === 0) { + return ( +

+ No runs yet. Trigger the bug-fix agent above to get started. +

+ ); + } + + return ( + + + + + + + + + + + + {runs.map((r) => ( + + + + + + + + ))} + +
RunAgentInputStatusStarted
+ {r.run_id.slice(0, 8)} + {r.agent}{r.label} + + {new Date(r.created_at).toLocaleString()}
+ ); +} diff --git a/services/hackbot-ui/components/RunDetail.tsx b/services/hackbot-ui/components/RunDetail.tsx new file mode 100644 index 0000000000..be0bdbf3ca --- /dev/null +++ b/services/hackbot-ui/components/RunDetail.tsx @@ -0,0 +1,179 @@ +"use client"; + +import Link from "next/link"; +import { useCallback, useEffect, useRef, useState } from "react"; + +import { updateRunStatus } from "@/lib/store"; +import { isTerminal, type RunDoc } from "@/lib/types"; +import { StatusBadge } from "./StatusBadge"; + +const POLL_MS = 4000; + +function formatBytes(n: number): string { + if (n < 1024) return `${n} B`; + if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; + return `${(n / (1024 * 1024)).toFixed(1)} MB`; +} + +// Link to the proxy route, which redirects to a signed download URL. Each path +// segment is encoded individually so subfolders survive the catch-all route. +function artifactHref(runId: string, name: string): string { + const encoded = name.split("/").map(encodeURIComponent).join("/"); + return `/api/runs/${encodeURIComponent(runId)}/artifacts/${encoded}`; +} + +// The agent's completion output lives in summary.findings. We surface a +// free-text "log"/"output" field as a log pane when present, and always show +// the full structured findings as JSON. +function extractLog(run: RunDoc): string | null { + const f = run.summary?.findings; + if (!f) return null; + for (const key of ["log", "output", "transcript", "stdout", "message"]) { + const v = (f as Record)[key]; + if (typeof v === "string" && v.trim()) return v; + } + return null; +} + +export function RunDetail({ runId }: { runId: string }) { + const [run, setRun] = useState(null); + const [error, setError] = useState(null); + const [polling, setPolling] = useState(true); + const timer = useRef | null>(null); + + const fetchRun = useCallback(async () => { + try { + const res = await fetch(`/api/runs/${runId}`); + const body = await res.json(); + if (!res.ok) + throw new Error(body?.error ?? `Request failed (${res.status})`); + const doc = body as RunDoc; + setRun(doc); + setError(null); + updateRunStatus(runId, doc.status); + if (isTerminal(doc.status)) { + setPolling(false); + return false; + } + return true; + } catch (err) { + setError((err as Error).message); + return true; // keep retrying transient errors + } + }, [runId]); + + useEffect(() => { + let cancelled = false; + async function loop() { + const keepGoing = await fetchRun(); + if (!cancelled && keepGoing) { + timer.current = setTimeout(loop, POLL_MS); + } + } + loop(); + return () => { + cancelled = true; + if (timer.current) clearTimeout(timer.current); + }; + }, [fetchRun]); + + if (!run && error) { + return
{error}
; + } + if (!run) { + return

Loading run…

; + } + + const log = extractLog(run); + const findings = run.summary?.findings ?? {}; + const hasFindings = Object.keys(findings).length > 0; + + return ( + <> +
+
+ + {polling && ( + + live — refreshing every {POLL_MS / 1000}s + + )} +
+ + ← all runs + +
+ + {error &&
Refresh error: {error}
} + +
+

Run

+
+
Run ID
+
{run.run_id}
+
Agent
+
{run.agent}
+
Inputs
+
{JSON.stringify(run.inputs)}
+
Created
+
{new Date(run.created_at).toLocaleString()}
+
Updated
+
{new Date(run.updated_at).toLocaleString()}
+ {run.execution_name && ( + <> +
Execution
+
{run.execution_name}
+ + )} +
+
+ + {run.error && ( +
+

Error

+
{run.error}
+
+ )} + + {log && ( +
+

Agent log

+
{log}
+
+ )} + + {hasFindings && ( +
+

Findings

+
{JSON.stringify(findings, null, 2)}
+
+ )} + +
+

Artifacts ({run.artifacts.length})

+ {run.artifacts.length === 0 ? ( +

+ {isTerminal(run.status) + ? "No artifacts were produced." + : "Artifacts appear once the run completes."} +

+ ) : ( +
    + {run.artifacts.map((a) => ( +
  • + + {a.name} + + {formatBytes(a.size)} +
  • + ))} +
+ )} +
+ + ); +} diff --git a/services/hackbot-ui/components/StatusBadge.tsx b/services/hackbot-ui/components/StatusBadge.tsx new file mode 100644 index 0000000000..55103b931c --- /dev/null +++ b/services/hackbot-ui/components/StatusBadge.tsx @@ -0,0 +1,13 @@ +import type { RunStatus } from "@/lib/types"; + +const LABEL: Record = { + pending: "pending", + running: "running", + succeeded: "succeeded", + failed: "failed", + timed_out: "timed out", +}; + +export function StatusBadge({ status }: { status: RunStatus }) { + return {LABEL[status] ?? status}; +} diff --git a/services/hackbot-ui/components/TriggerForm.tsx b/services/hackbot-ui/components/TriggerForm.tsx new file mode 100644 index 0000000000..14b4bb1927 --- /dev/null +++ b/services/hackbot-ui/components/TriggerForm.tsx @@ -0,0 +1,118 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +import { saveRun } from "@/lib/store"; +import type { RunRef } from "@/lib/types"; + +// The demo currently exposes the bug-fix agent. The form maps directly to +// BugFixInputs in hackbot-api (bug_id required; model/max_turns/effort optional). +const AGENT = "bug-fix"; + +export function TriggerForm() { + const router = useRouter(); + const [bugId, setBugId] = useState(""); + const [model, setModel] = useState(""); + const [maxTurns, setMaxTurns] = useState(""); + const [effort, setEffort] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + async function onSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(null); + + const parsedBugId = Number.parseInt(bugId, 10); + if (!Number.isInteger(parsedBugId) || parsedBugId <= 0) { + setError("Enter a valid Bugzilla bug ID."); + return; + } + + const inputs: Record = { bug_id: parsedBugId }; + if (model.trim()) inputs.model = model.trim(); + if (maxTurns.trim()) { + const n = Number.parseInt(maxTurns, 10); + if (Number.isInteger(n) && n > 0) inputs.max_turns = n; + } + if (effort.trim()) inputs.effort = effort.trim(); + + setSubmitting(true); + try { + const res = await fetch("/api/runs", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ agent: AGENT, inputs }), + }); + const body = await res.json(); + if (!res.ok) { + throw new Error(body?.error ?? `Request failed (${res.status})`); + } + const run = body as RunRef; + saveRun({ + run_id: run.run_id, + agent: run.agent, + status: run.status, + label: `bug ${parsedBugId}`, + created_at: new Date().toISOString(), + }); + router.push(`/runs/${run.run_id}`); + } catch (err) { + setError((err as Error).message); + setSubmitting(false); + } + } + + return ( +
+ {error &&
{error}
} + +
+ + setBugId(e.target.value)} + required + /> +
+ +
+
+ + setModel(e.target.value)} + /> +
+
+ + setMaxTurns(e.target.value)} + /> +
+
+ + setEffort(e.target.value)} + /> +
+
+ + +
+ ); +} diff --git a/services/hackbot-ui/components/UserMenu.tsx b/services/hackbot-ui/components/UserMenu.tsx new file mode 100644 index 0000000000..183ab88a16 --- /dev/null +++ b/services/hackbot-ui/components/UserMenu.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useRouter } from "next/navigation"; + +import { signOut, useSession } from "@/lib/auth-client"; + +export function UserMenu() { + const router = useRouter(); + const { data: session, isPending } = useSession(); + + if (isPending || !session?.user) { + return null; + } + + async function onSignOut() { + await signOut(); + router.push("/login"); + router.refresh(); + } + + return ( +
+ + {session.user.email} + + +
+ ); +} diff --git a/services/hackbot-ui/deploy.sh b/services/hackbot-ui/deploy.sh new file mode 100755 index 0000000000..f7b23ad928 --- /dev/null +++ b/services/hackbot-ui/deploy.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# +# Deploy the Hackbot Console to Cloud Run. +# +# Prereqs (one-time): +# gcloud auth login +# gcloud config set project +# gcloud services enable run.googleapis.com artifactregistry.googleapis.com \ +# cloudbuild.googleapis.com secretmanager.googleapis.com +# +# Secrets (one-time) — store sensitive values in Secret Manager. +# The hackbot-api X-API-Key reuses the EXISTING `external-api-key` secret +# (override with API_KEY_SECRET=...); only these two are UI-specific: +# printf '%s' "$(openssl rand -base64 32)" | gcloud secrets create hackbot-ui-auth-secret --data-file=- +# printf '%s' '' | gcloud secrets create hackbot-ui-google-secret --data-file=- +# +# This script creates a dedicated least-privilege runtime service account +# (hackbot-ui-run@...) and grants it secretAccessor on the three secrets it reads. +# The identity running this script needs roles/run.admin, roles/iam.serviceAccountUser +# on that SA (to deploy a service that runs as it), and rights to manage secrets. +# +# Usage: +# PROJECT=my-proj REGION=us-central1 \ +# HACKBOT_API_URL=https://hackbot-api-xxxx.run.app \ +# GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.com \ +# ./deploy.sh +# +# After the FIRST deploy, copy the printed service URL into BETTER_AUTH_URL +# (and the Google OAuth redirect URI), then re-run this script. +set -euo pipefail + +PROJECT="${PROJECT:?set PROJECT to your GCP project id}" +REGION="${REGION:-us-central1}" +SERVICE="${SERVICE:-hackbot-ui}" +REPO="${REPO:-hackbot}" +HACKBOT_API_URL="${HACKBOT_API_URL:?set HACKBOT_API_URL to the hackbot-api base URL}" +GOOGLE_CLIENT_ID="${GOOGLE_CLIENT_ID:?set GOOGLE_CLIENT_ID}" +# BETTER_AUTH_URL is optional on the first deploy; set it on the second pass. +BETTER_AUTH_URL="${BETTER_AUTH_URL:-}" + +# Dedicated, least-privilege runtime identity for the UI (only needs to read +# its secrets). Override SA_NAME/SA_EMAIL to use an existing account. +SA_NAME="${SA_NAME:-hackbot-ui-run}" +SA_EMAIL="${SA_EMAIL:-${SA_NAME}@${PROJECT}.iam.gserviceaccount.com}" + +# Secret Manager secret names. The API key reuses the existing shared secret. +AUTH_SECRET="${AUTH_SECRET:-hackbot-ui-auth-secret}" +API_KEY_SECRET="${API_KEY_SECRET:-external-api-key}" +GOOGLE_SECRET="${GOOGLE_SECRET:-hackbot-ui-google-secret}" + +IMAGE="${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/${SERVICE}:latest" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "==> Ensuring runtime service account '${SA_EMAIL}' exists" +gcloud iam service-accounts describe "${SA_EMAIL}" >/dev/null 2>&1 || \ + gcloud iam service-accounts create "${SA_NAME}" \ + --display-name="Hackbot Console (Cloud Run runtime)" + +echo "==> Granting the SA read access to its secrets" +for s in "${AUTH_SECRET}" "${API_KEY_SECRET}" "${GOOGLE_SECRET}"; do + gcloud secrets add-iam-policy-binding "$s" \ + --member="serviceAccount:${SA_EMAIL}" \ + --role=roles/secretmanager.secretAccessor >/dev/null +done + +echo "==> Ensuring Artifact Registry repo '${REPO}' exists in ${REGION}" +gcloud artifacts repositories describe "${REPO}" --location="${REGION}" >/dev/null 2>&1 || \ + gcloud artifacts repositories create "${REPO}" \ + --repository-format=docker --location="${REGION}" \ + --description="Hackbot container images" + +echo "==> Building & pushing image with Cloud Build: ${IMAGE}" +gcloud builds submit "${SCRIPT_DIR}" --tag "${IMAGE}" + +echo "==> Deploying to Cloud Run" +ENV_VARS="HACKBOT_API_URL=${HACKBOT_API_URL},GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}" +[ -n "${BETTER_AUTH_URL}" ] && ENV_VARS="${ENV_VARS},BETTER_AUTH_URL=${BETTER_AUTH_URL}" + +gcloud run deploy "${SERVICE}" \ + --image "${IMAGE}" \ + --region "${REGION}" \ + --platform managed \ + --allow-unauthenticated \ + --port 8080 \ + --service-account "${SA_EMAIL}" \ + --set-env-vars "${ENV_VARS}" \ + --set-secrets "BETTER_AUTH_SECRET=${AUTH_SECRET}:latest,HACKBOT_API_KEY=${API_KEY_SECRET}:latest,GOOGLE_CLIENT_SECRET=${GOOGLE_SECRET}:latest" + +URL="$(gcloud run services describe "${SERVICE}" --region "${REGION}" --format='value(status.url)')" +echo +echo "==> Deployed: ${URL}" +if [ -z "${BETTER_AUTH_URL}" ]; then + echo "NEXT STEPS (first deploy):" + echo " 1. Re-run with BETTER_AUTH_URL=${URL}" + echo " 2. Add this to your Google OAuth client's authorized redirect URIs:" + echo " ${URL}/api/auth/callback/google" +fi diff --git a/services/hackbot-ui/lib/auth-client.ts b/services/hackbot-ui/lib/auth-client.ts new file mode 100644 index 0000000000..a90f6b9e11 --- /dev/null +++ b/services/hackbot-ui/lib/auth-client.ts @@ -0,0 +1,7 @@ +"use client"; + +import { createAuthClient } from "better-auth/react"; + +export const authClient = createAuthClient(); + +export const { signIn, signOut, useSession } = authClient; diff --git a/services/hackbot-ui/lib/auth.ts b/services/hackbot-ui/lib/auth.ts new file mode 100644 index 0000000000..0ed07cc0e0 --- /dev/null +++ b/services/hackbot-ui/lib/auth.ts @@ -0,0 +1,51 @@ +import { betterAuth } from "better-auth"; +import { APIError } from "better-auth/api"; + +// Only Mozilla staff may use the app. +const ALLOWED_DOMAIN = "@mozilla.com"; + +export function isAllowedEmail(email: string | null | undefined): boolean { + return ( + typeof email === "string" && email.toLowerCase().endsWith(ALLOWED_DOMAIN) + ); +} + +export const auth = betterAuth({ + // Sign-in is exclusively via Google; no email/password. + emailAndPassword: { enabled: false }, + // These are auto-enabled when no database is present, but we set them + // explicitly so the stateless intent is obvious. + session: { + cookieCache: { + enabled: true, + strategy: "jwe", + refreshCache: true, + maxAge: 7 * 24 * 60 * 60, // 7 days + }, + }, + account: { + // Persist OAuth account state in a cookie instead of a database. + storeAccountCookie: true, + }, + socialProviders: { + google: { + clientId: process.env.GOOGLE_CLIENT_ID as string, + clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, + // `hd` hints Google to preselect Mozilla accounts. It is only a hint — + // the authoritative checks are mapProfileToUser (below) and the + // per-request domain check in lib/session.ts. + hd: "mozilla.com", + // Reject any non-mozilla.com identity during the OAuth callback, before + // a session is issued. This runs in stateless mode (unlike databaseHooks, + // which only fire when a database adapter is configured). + mapProfileToUser: (profile) => { + if (!isAllowedEmail(profile.email)) { + throw new APIError("FORBIDDEN", { + message: "Access is restricted to @mozilla.com accounts.", + }); + } + return { email: profile.email }; + }, + }, + }, +}); diff --git a/services/hackbot-ui/lib/hackbot.ts b/services/hackbot-ui/lib/hackbot.ts new file mode 100644 index 0000000000..e158c7a7d0 --- /dev/null +++ b/services/hackbot-ui/lib/hackbot.ts @@ -0,0 +1,104 @@ +import "server-only"; + +import type { AgentDescriptor, RunDoc, RunRef } from "./types"; + +// Thin server-side client for the hackbot-api. The API key lives here and is +// never exposed to the browser — every browser request goes through the +// /api/* route handlers, which call into this module. + +export class HackbotError extends Error { + constructor( + message: string, + readonly status: number, + ) { + super(message); + this.name = "HackbotError"; + } +} + +function config(): { baseUrl: string; apiKey: string } { + const baseUrl = process.env.HACKBOT_API_URL; + const apiKey = process.env.HACKBOT_API_KEY; + if (!baseUrl) { + throw new HackbotError("HACKBOT_API_URL is not configured", 500); + } + if (!apiKey) { + throw new HackbotError("HACKBOT_API_KEY is not configured", 500); + } + return { baseUrl: baseUrl.replace(/\/$/, ""), apiKey }; +} + +async function request( + path: string, + init: RequestInit = {}, +): Promise { + const { baseUrl, apiKey } = config(); + const res = await fetch(`${baseUrl}${path}`, { + ...init, + headers: { + "X-API-Key": apiKey, + "Content-Type": "application/json", + ...(init.headers ?? {}), + }, + // Always hit the upstream fresh; run state changes over time. + cache: "no-store", + }); + + if (!res.ok) { + let detail = `${res.status} ${res.statusText}`; + try { + const body = await res.json(); + if (body?.detail) { + detail = + typeof body.detail === "string" + ? body.detail + : JSON.stringify(body.detail); + } + } catch { + // non-JSON error body; keep the status line + } + throw new HackbotError(detail, res.status); + } + + if (res.status === 204) { + return undefined as T; + } + return (await res.json()) as T; +} + +export function listAgents(): Promise { + return request("/agents"); +} + +export function createRun( + agentName: string, + inputs: Record, +): Promise { + return request( + `/agents/${encodeURIComponent(agentName)}/runs`, + { + method: "POST", + body: JSON.stringify(inputs), + }, + ); +} + +export function getRun(runId: string): Promise { + return request(`/runs/${encodeURIComponent(runId)}`); +} + +// Ask hackbot-api for a short-lived signed download URL for one artifact. +// `artifactName` may contain slashes; each segment is encoded individually so +// the upstream `{artifact_path:path}` route still sees the directory structure. +export function getArtifactDownloadUrl( + runId: string, + artifactName: string, +): Promise<{ url: string }> { + const encodedPath = artifactName + .split("/") + .map(encodeURIComponent) + .join("/"); + return request<{ url: string }>( + `/runs/${encodeURIComponent(runId)}/artifacts/${encodedPath}`, + ); +} diff --git a/services/hackbot-ui/lib/session.ts b/services/hackbot-ui/lib/session.ts new file mode 100644 index 0000000000..efcb3f891a --- /dev/null +++ b/services/hackbot-ui/lib/session.ts @@ -0,0 +1,13 @@ +import "server-only"; + +import { headers } from "next/headers"; + +import { auth, isAllowedEmail } from "./auth"; + +// Authoritative session check used by the proxy API routes. Validates the +// session cookie (not just its presence) and re-enforces the domain allowlist. +export async function getAuthedEmail(): Promise { + const session = await auth.api.getSession({ headers: await headers() }); + const email = session?.user?.email ?? null; + return isAllowedEmail(email) ? email : null; +} diff --git a/services/hackbot-ui/lib/store.ts b/services/hackbot-ui/lib/store.ts new file mode 100644 index 0000000000..98ae654b4e --- /dev/null +++ b/services/hackbot-ui/lib/store.ts @@ -0,0 +1,55 @@ +"use client"; + +// hackbot-api has no "list runs" endpoint, so the app remembers the runs +// it has triggered in the browser's localStorage. This is intentionally +// lightweight — it is a demonstration app, not a system of record. + +import type { RunStatus } from "./types"; + +const KEY = "hackbot-ui.runs"; + +export interface TrackedRun { + run_id: string; + agent: string; + status: RunStatus; + // Human-readable summary of the inputs, e.g. "bug 1846789". + label: string; + created_at: string; +} + +export function loadRuns(): TrackedRun[] { + if (typeof window === "undefined") return []; + try { + const raw = window.localStorage.getItem(KEY); + if (!raw) return []; + const parsed = JSON.parse(raw) as TrackedRun[]; + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } +} + +export function saveRun(run: TrackedRun): TrackedRun[] { + const runs = loadRuns().filter((r) => r.run_id !== run.run_id); + runs.unshift(run); + const trimmed = runs.slice(0, 50); + window.localStorage.setItem(KEY, JSON.stringify(trimmed)); + return trimmed; +} + +export function updateRunStatus( + runId: string, + status: RunStatus, +): TrackedRun[] { + const runs = loadRuns().map((r) => + r.run_id === runId ? { ...r, status } : r, + ); + window.localStorage.setItem(KEY, JSON.stringify(runs)); + return runs; +} + +export function removeRun(runId: string): TrackedRun[] { + const runs = loadRuns().filter((r) => r.run_id !== runId); + window.localStorage.setItem(KEY, JSON.stringify(runs)); + return runs; +} diff --git a/services/hackbot-ui/lib/types.ts b/services/hackbot-ui/lib/types.ts new file mode 100644 index 0000000000..033eb64dc0 --- /dev/null +++ b/services/hackbot-ui/lib/types.ts @@ -0,0 +1,57 @@ +// Mirror of the hackbot-api response models (services/hackbot-api/app/schemas.py). + +export type RunStatus = + | "pending" + | "running" + | "succeeded" + | "failed" + | "timed_out"; + +export const TERMINAL_STATUSES: RunStatus[] = [ + "succeeded", + "failed", + "timed_out", +]; + +export function isTerminal(status: RunStatus): boolean { + return TERMINAL_STATUSES.includes(status); +} + +export interface AgentDescriptor { + name: string; + description: string; + // JSON Schema describing the agent's accepted inputs. + input_schema: Record; +} + +export interface ArtifactRef { + name: string; + size: number; + content_type: string | null; +} + +export interface RunSummary { + status: string; + error: string | null; + findings: Record; +} + +export interface RunRef { + run_id: string; + agent: string; + status: RunStatus; +} + +export interface RunDoc { + run_id: string; + agent: string; + status: RunStatus; + inputs: Record; + created_at: string; + updated_at: string; + execution_name: string | null; + results_prefix: string; + summary: RunSummary | null; + artifacts: ArtifactRef[]; + error: string | null; +} diff --git a/services/hackbot-ui/middleware.ts b/services/hackbot-ui/middleware.ts new file mode 100644 index 0000000000..11cfecc912 --- /dev/null +++ b/services/hackbot-ui/middleware.ts @@ -0,0 +1,27 @@ +import { getSessionCookie } from "better-auth/cookies"; +import { NextRequest, NextResponse } from "next/server"; + +// Optimistic auth guard for the UI. This only checks for the presence of a +// valid session cookie — the proxy API routes additionally validate the +// session and the @mozilla.com allowlist server-side (see lib/session.ts). +export function middleware(req: NextRequest) { + const sessionCookie = getSessionCookie(req); + if (sessionCookie) { + return NextResponse.next(); + } + + // For data routes, reply with JSON so client fetches see a clean 401 rather + // than following a redirect to the login HTML page. + if (req.nextUrl.pathname.startsWith("/api/")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const loginUrl = new URL("/login", req.url); + return NextResponse.redirect(loginUrl); +} + +export const config = { + // Protect everything except the auth endpoints, the login page, and static + // assets. + matcher: ["/((?!api/auth|login|_next/static|_next/image|favicon.ico).*)"], +}; diff --git a/services/hackbot-ui/next-env.d.ts b/services/hackbot-ui/next-env.d.ts new file mode 100644 index 0000000000..830fb594ca --- /dev/null +++ b/services/hackbot-ui/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/services/hackbot-ui/next.config.mjs b/services/hackbot-ui/next.config.mjs new file mode 100644 index 0000000000..c4f93eca2b --- /dev/null +++ b/services/hackbot-ui/next.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + // Emit a self-contained server bundle so the Docker image stays small. + output: "standalone", + reactStrictMode: true, +}; + +export default nextConfig; diff --git a/services/hackbot-ui/package-lock.json b/services/hackbot-ui/package-lock.json new file mode 100644 index 0000000000..170aae6452 --- /dev/null +++ b/services/hackbot-ui/package-lock.json @@ -0,0 +1,1790 @@ +{ + "name": "hackbot-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hackbot-ui", + "version": "0.1.0", + "dependencies": { + "better-auth": "^1.6.0", + "next": "^15.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "server-only": "^0.0.1" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "typescript": "^5.7.0" + } + }, + "node_modules/@better-auth/core": { + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.14.tgz", + "integrity": "sha512-12cA7tnR4Wyb3nLpPmeq/Id7QNB+4OhjbzuX7sIhqglgXGjyT5iiNpe2lx/8FF532sHC450Yx1850salCYbkzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.39.0", + "@standard-schema/spec": "^1.1.0", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@better-auth/utils": "0.4.1", + "@better-fetch/fetch": "1.1.21", + "@cloudflare/workers-types": ">=4", + "@opentelemetry/api": "^1.9.0", + "better-call": "1.3.5", + "jose": "^6.1.0", + "kysely": "^0.28.5 || ^0.29.0", + "nanostores": "^1.0.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@better-auth/drizzle-adapter": { + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.14.tgz", + "integrity": "sha512-lYs1jDudriKYMXNcLFLAvEvOEKbeKBFdDciG4H8qZhV+3+yghGC3f/H5qtgTDc8mGBPV+2tEvVgYqReurOSmNw==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.14", + "@better-auth/utils": "0.4.1", + "drizzle-orm": "^0.45.2" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": true + } + } + }, + "node_modules/@better-auth/kysely-adapter": { + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.14.tgz", + "integrity": "sha512-A2+381gYADuZpgd98XQ39bnxLzbT03wnnDmSQIXp7XcE3hF093mGMk6rxlAhENVHH7JL2B0Tv2la2o6n+6ppyQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.14", + "@better-auth/utils": "0.4.1", + "kysely": "^0.28.17 || ^0.29.0" + }, + "peerDependenciesMeta": { + "kysely": { + "optional": true + } + } + }, + "node_modules/@better-auth/memory-adapter": { + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.14.tgz", + "integrity": "sha512-frtBTozi8qsBlypxp33dkiIZT2IOMvix3oh2qTTcBkK11ISsRSTUUadl7DbwXri2AEoooShsH6PSAput920J3Q==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.14", + "@better-auth/utils": "0.4.1" + } + }, + "node_modules/@better-auth/mongo-adapter": { + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.14.tgz", + "integrity": "sha512-meaZx712k9c0Cl6urwYZRNa3mAy3/leaYiSNt+hVaCOEPlgTDxzmYMNACvTTYXgh4eCpDVf5G7ZMEYBtejKQdw==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.14", + "@better-auth/utils": "0.4.1", + "mongodb": "^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "mongodb": { + "optional": true + } + } + }, + "node_modules/@better-auth/prisma-adapter": { + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.14.tgz", + "integrity": "sha512-9b9wSqhCthMmOYo0QdX+N/cOv+fNck/JE5CZQuuWwEJl5QeoYhCZesXjts5VfLAPMIf6vKw3QNBrn0SVMXXi2Q==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.14", + "@better-auth/utils": "0.4.1", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@prisma/client": { + "optional": true + }, + "prisma": { + "optional": true + } + } + }, + "node_modules/@better-auth/telemetry": { + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.14.tgz", + "integrity": "sha512-ALi3cEx5eyrFY+TeAdhc1uq8FqJyGvzgvIo7GQZOqGqLZxHY9nte44WN++jBFGJJbsW3e4cgLj8dQK291s6wWQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.14", + "@better-auth/utils": "0.4.1", + "@better-fetch/fetch": "1.1.21" + } + }, + "node_modules/@better-auth/utils": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.1.tgz", + "integrity": "sha512-SZBPRPF3z0nBvE5ygOkxae35wnnXPRShmqFo78S+qslLeFoPu/pMgnXAuNKFMMybac3tiLaVg1e3MQW5MC+1iA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "^2.0.1" + } + }, + "node_modules/@better-fetch/fetch": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.21.tgz", + "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==", + "peer": true + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.19.tgz", + "integrity": "sha512-sWWluFvcv5v3Fxznmf2ZfjyoVQt/64oCnYqS90inQWGzMPK1VjvekPiz3OPHKmFT30EnHrjlbyaHLt3M0vWabw==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.19.tgz", + "integrity": "sha512-jx9wWlTKueHKPvVOndyr7WuaevWCkuYqsQ8gC0TMPKAVWG3MhcdMrjfo9tvIZNXd0QOUYXXvAcZ325y8Uq7uzg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.19.tgz", + "integrity": "sha512-291KFcsIQ3OenRdiUDFOR6W3wezzH4auENXm1gbm1Bjd4ANMMRgxPrWTUztQN43BnVoVuMnHCrLeECIMwgFKbA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.19.tgz", + "integrity": "sha512-WeH+nelQyyMeE2f8FxBRZNrGipya5zHZV2vjzfCOAYyiI6am+NbnWAAldOBFQBB2w0DjJcsvrKqoFT2b7+5YoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.19.tgz", + "integrity": "sha512-5xTOE0lDlDCSSfp+BAif7j17VRRCjWp//ZPZy6NI0QpdrhxtQnsZguSx0xAAZ0c9XZLrLLwCe/XVe5YPrRilKw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.19.tgz", + "integrity": "sha512-LTxRmMgqqMv05Had879W00Fm53quiJd3Zuz8h1JSNJ3nGSlbZ/7Tjs1tKyScgN3Au3t3MyPsjPlq60fMmSHLsg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.19.tgz", + "integrity": "sha512-eoNQSpA5PQfB9wBO4RA47MTDXWz1fizy9Y3Z6e4DetYIF3dvjuu8sj7aIGn/bFCU6lnFzTK34NtCaffP4NsQ7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.19.tgz", + "integrity": "sha512-6UNt2dFuCHOe446sm/Kp69nUe8/wIhnh9bm6Xcqw4qEWCOppLMOvhTBVgvM7invVUNr4SPpP6NOQsACtn2IN9Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.19.tgz", + "integrity": "sha512-PhmojAHyqMne56HBLGu9dhDnHPuFmEjrXSQMM/nW0J6j849lk3ESrVtqNJcCk8CKOV7brpTTbaYAjwKPzKM69w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/ciphers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", + "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", + "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/better-auth": { + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.14.tgz", + "integrity": "sha512-c0/DvTQGDpgfj1knekCpQrg6PSWGDtfAtP7Ou6FkAhoE3RNnnIxLB5qKj6tRg53a1xsq93G6T68cNxrUZ7ZVmw==", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.6.14", + "@better-auth/drizzle-adapter": "1.6.14", + "@better-auth/kysely-adapter": "1.6.14", + "@better-auth/memory-adapter": "1.6.14", + "@better-auth/mongo-adapter": "1.6.14", + "@better-auth/prisma-adapter": "1.6.14", + "@better-auth/telemetry": "1.6.14", + "@better-auth/utils": "0.4.1", + "@better-fetch/fetch": "1.1.21", + "@noble/ciphers": "^2.1.1", + "@noble/hashes": "^2.0.1", + "better-call": "1.3.5", + "defu": "^6.1.4", + "jose": "^6.1.3", + "kysely": "^0.28.17 || ^0.29.0", + "nanostores": "^1.1.1", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@lynx-js/react": "*", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "@sveltejs/kit": "^2.0.0", + "@tanstack/react-start": "^1.0.0", + "@tanstack/solid-start": "^1.0.0", + "better-sqlite3": "^12.0.0", + "drizzle-kit": ">=0.31.4", + "drizzle-orm": "^0.45.2", + "mongodb": "^6.0.0 || ^7.0.0", + "mysql2": "^3.0.0", + "next": "^14.0.0 || ^15.0.0 || ^16.0.0", + "pg": "^8.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "solid-js": "^1.0.0", + "svelte": "^4.0.0 || ^5.0.0", + "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@lynx-js/react": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "@tanstack/react-start": { + "optional": true + }, + "@tanstack/solid-start": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "drizzle-kit": { + "optional": true + }, + "drizzle-orm": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "next": { + "optional": true + }, + "pg": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vitest": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/better-call": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.5.tgz", + "integrity": "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@better-auth/utils": "^0.4.0", + "@better-fetch/fetch": "^1.1.21", + "rou3": "^0.7.12", + "set-cookie-parser": "^3.0.1" + }, + "peerDependencies": { + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC", + "optional": true + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT", + "optional": true + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT", + "optional": true + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT", + "optional": true + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC", + "optional": true + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/kysely": { + "version": "0.28.17", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.17.tgz", + "integrity": "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanostores": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.3.0.tgz", + "integrity": "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT", + "optional": true + }, + "node_modules/next": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.19.tgz", + "integrity": "sha512-xNOW6tYshGX1/Oi3F8uuk4gpDeWsSUE/1Z0G5uUMekIxaQ0xc03UXd9II0VQHYMWviMeA0OHpJFAKsHf8bTYVg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@next/env": "15.5.19", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.19", + "@next/swc-darwin-x64": "15.5.19", + "@next/swc-linux-arm64-gnu": "15.5.19", + "@next/swc-linux-arm64-musl": "15.5.19", + "@next/swc-linux-x64-gnu": "15.5.19", + "@next/swc-linux-x64-musl": "15.5.19", + "@next/swc-win32-arm64-msvc": "15.5.19", + "@next/swc-win32-x64-msvc": "15.5.19", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rou3": { + "version": "0.7.12", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz", + "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "license": "MIT" + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/services/hackbot-ui/package.json b/services/hackbot-ui/package.json new file mode 100644 index 0000000000..90446afc64 --- /dev/null +++ b/services/hackbot-ui/package.json @@ -0,0 +1,28 @@ +{ + "name": "hackbot-ui", + "version": "0.1.0", + "private": true, + "description": "Demo web console for triaging agents through the hackbot-api.", + "scripts": { + "dev": "next dev -p 3000", + "build": "next build", + "start": "next start -p 3000", + "lint": "next lint" + }, + "dependencies": { + "better-auth": "^1.6.0", + "next": "^15.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "server-only": "^0.0.1" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "typescript": "^5.7.0" + }, + "overrides": { + "kysely": "0.28.17" + } +} diff --git a/services/hackbot-ui/public/.gitkeep b/services/hackbot-ui/public/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/hackbot-ui/tsconfig.json b/services/hackbot-ui/tsconfig.json new file mode 100644 index 0000000000..8c3a42db95 --- /dev/null +++ b/services/hackbot-ui/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}