diff --git a/.gitignore b/.gitignore index 235ca7d..ac91918 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ dist-ssr .opencode .todo.md -openapi.json \ No newline at end of file +.execute.md +.prd.md +openapi.json diff --git a/apps/cli/eslint.config.js b/apps/cli/eslint.config.js new file mode 100644 index 0000000..57f8cc4 --- /dev/null +++ b/apps/cli/eslint.config.js @@ -0,0 +1,4 @@ +import cliConfig from "@workspace/eslint-config/cli" + +/** @type {import("eslint").Linter.Config} */ +export default cliConfig diff --git a/cli/package.json b/apps/cli/package.json similarity index 54% rename from cli/package.json rename to apps/cli/package.json index bf1f901..15b8be9 100644 --- a/cli/package.json +++ b/apps/cli/package.json @@ -1,10 +1,16 @@ { - "name": "proxy-server", + "name": "opencode-web-cli", "version": "1.0.0", "description": "", + "bin": { + "opencode-web": "src/index.ts" + }, "main": "index.js", "type": "module", "scripts": { + "start": "tsx src/index.ts", + "dev": "tsx src/index.ts", + "lint": "eslint .", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], @@ -16,5 +22,11 @@ "get-port": "^7.1.0", "http-proxy-middleware": "^3.0.5", "yargs": "^18.0.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/yargs": "^17.0.24", + "tsx": "^4.20.3" } } diff --git a/cli/pnpm-lock.yaml b/apps/cli/pnpm-lock.yaml similarity index 100% rename from cli/pnpm-lock.yaml rename to apps/cli/pnpm-lock.yaml diff --git a/apps/cli/src/broker/index.ts b/apps/cli/src/broker/index.ts new file mode 100644 index 0000000..6b660ce --- /dev/null +++ b/apps/cli/src/broker/index.ts @@ -0,0 +1,30 @@ +#!/usr/bin/env tsx +import cors from "cors" +import express from "express" + +import { BROKER_HOST } from "../lib.js" +import type { Instance } from "../types" +import createProcRoutes from "./routes/proc-routes.js" +import createPublicRoutes from "./routes/public-routes.js" + +// ESM __dirname shim + +const port = parseInt(process.argv[2] ?? "", 10) +if (!port) { + console.error("Broker: No port provided. Usage: broker.js ") + process.exit(1) +} + +const app = express() +app.use(cors()) +app.use(express.json()) + +let instances: Instance[] = [] + +// mount route modules +app.use("/", createProcRoutes(instances)) +app.use("/", createPublicRoutes(instances)) + +app.listen(port, BROKER_HOST, () => { + console.log(`Broker running at http://${BROKER_HOST}:${port}`) +}) diff --git a/apps/cli/src/broker/routes/proc-routes.ts b/apps/cli/src/broker/routes/proc-routes.ts new file mode 100644 index 0000000..d0de889 --- /dev/null +++ b/apps/cli/src/broker/routes/proc-routes.ts @@ -0,0 +1,57 @@ +import { Router } from "express" + +import { BROKER_HOST } from "../../lib" +import type { Instance } from "../../types" + +export default function createProcRoutes(instances: Instance[]) { + const router = Router() + + router.post("/register", (req, res) => { + const { cwd, port: instancePort } = req.body as { + cwd?: string + port?: number + } + if (!cwd || !instancePort) + return res.status(400).send("Missing cwd or port") + let inst = instances.find((i) => i.cwd === cwd) + // in case port changes for a cwd + if (inst) { + inst.port = instancePort + inst.lastSeen = Date.now() + inst.status = "online" + inst.startedAt = Date.now() + } else { + instances.push({ + cwd, + port: instancePort, + host: BROKER_HOST, + status: "online", + lastSeen: Date.now(), + startedAt: Date.now(), + }) + } + res.sendStatus(200) + }) + + router.post("/ping", (req, res) => { + const { cwd, port: instancePort } = req.body as { + cwd?: string + port?: number + } + if (!cwd || !instancePort) + return res.status(400).send("Missing cwd or port") + const inst = instances.find((i) => i.cwd === cwd && i.port === instancePort) + if (inst) inst.lastSeen = Date.now() + res.sendStatus(200) + }) + + router.post("/deregister", (req, res) => { + const { cwd } = req.body as { cwd?: string } + if (!cwd) return res.status(400).send("Missing cwd") + const inst = instances.find((i) => i.cwd === cwd) + if (inst) inst.status = "offline" + res.json({ instances }) + }) + + return router +} diff --git a/apps/cli/src/broker/routes/public-routes.ts b/apps/cli/src/broker/routes/public-routes.ts new file mode 100644 index 0000000..edd6a09 --- /dev/null +++ b/apps/cli/src/broker/routes/public-routes.ts @@ -0,0 +1,109 @@ +import { spawn } from "child_process" +import path from "path" +import { fileURLToPath } from "url" +import { Router } from "express" + +import type { Instance } from "../../types" + +const __filename = fileURLToPath(import.meta.url) +const ROUTES_DIR = path.dirname(__filename) +const BROKER_DIR = path.dirname(ROUTES_DIR) // up one level to broker folder + +export default function createPublicRoutes(instances: Instance[]) { + const router = Router() + + // helper to mark stale + const INSTANCE_STALE_TIMEOUT_MS = 30000 + function updateInstanceStatus() { + const now = Date.now() + for (const inst of instances) { + if (inst.lastSeen && now - inst.lastSeen > INSTANCE_STALE_TIMEOUT_MS) { + inst.status = "offline" + } + } + } + + router.get("/instances", (_req, res) => { + updateInstanceStatus() + res.json({ version: "1.0.0", info: { name: "opencode-web" }, instances }) + }) + + // Helper to wait for registration/offline + function waitFor(cond: () => boolean, timeoutMs = 10000): Promise { + return new Promise((resolve) => { + const start = Date.now() + const timer = setInterval(() => { + if (cond()) { + clearInterval(timer) + resolve(true) + } else if (Date.now() - start > timeoutMs) { + clearInterval(timer) + resolve(false) + } + }, 200) + }) + } + + // POST /instance -> start + router.post("/instance", async (req, res) => { + const { cwd } = req.body as { cwd?: string } + if (!cwd) return res.status(400).send("Missing cwd") + + const existing = instances.find( + (i) => i.cwd === cwd && i.status === "online" + ) + if (existing) + return res.status(409).json({ message: "Instance already running" }) + + const daemonPath = path.join(BROKER_DIR, "..", "opencode-proc", "index.ts") + const proc = spawn("tsx", [daemonPath, cwd], { + cwd, + detached: false, + stdio: "ignore", + shell: true, + }) + + const ok = await waitFor(() => + Boolean(instances.find((i) => i.cwd === cwd && i.status === "online")) + ) + + if (ok) { + proc.unref() + const inst = instances.find( + (i) => i.cwd === cwd && i.status === "online" + )! + return res + .status(201) + .json({ message: "Instance ready", port: inst.port }) + } + + try { + proc.kill() + } catch {} + return res.status(500).json({ error: "startup timeout" }) + }) + + // DELETE /instance -> stop + router.delete("/instance", async (req, res) => { + const { cwd } = req.body as { cwd?: string } + if (!cwd) return res.status(400).send("Missing cwd") + + const inst = instances.find((i) => i.cwd === cwd && i.status === "online") + if (!inst) return res.status(404).json({ message: "Instance not found" }) + + try { + await fetch(`http://localhost:${inst.port}/__shutdown`, { + method: "POST", + }) + } catch {} + + const ok = await waitFor( + () => !instances.find((i) => i.cwd === cwd && i.status === "online") + ) + + if (ok) return res.json({ message: "Instance stopped" }) + return res.status(500).json({ error: "shutdown timeout" }) + }) + + return router +} diff --git a/apps/cli/src/cli-client/index.ts b/apps/cli/src/cli-client/index.ts new file mode 100644 index 0000000..cb669e9 --- /dev/null +++ b/apps/cli/src/cli-client/index.ts @@ -0,0 +1,180 @@ +#!/usr/bin/env node +import fs from "fs" +import path from "path" +import { hideBin } from "yargs/helpers" +import yargs from "yargs/yargs" + +import { BROKER_HOST } from "../lib" +import type { Instance } from "../types" +import { ensureBroker } from "../utils" + +export async function fetchInstances(brokerPort: number): Promise<{ + instances: Instance[] +} | null> { + try { + const res = await fetch(`http://${BROKER_HOST}:${brokerPort}/instances`) + if (!res.ok) return null + const data = (await res.json()) as any + return { instances: (data.instances ?? []) as Instance[] } + } catch { + return null + } +} + +export async function stopByInstanceCwd(brokerPort: number, cwd: string) { + const res = await fetch(`http://${BROKER_HOST}:${brokerPort}/instance`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cwd }), + }) + if (res.status === 404) { + console.log(`No instance for ${cwd}`) + process.exit(1) + } + if (!res.ok) { + console.error(`Failed to stop instance: ${await res.text()}`) + process.exit(1) + } + console.log(`Stopped ${cwd}`) +} + +async function handleList() { + const brokerPort = await ensureBroker() + const data = await fetchInstances(brokerPort) + if (!data) { + console.log("No broker running.") + return + } + const runningInstances = data.instances.filter((i) => i.status === "online") + + if (runningInstances.length === 0) { + console.log("No instances running.") + return + } + console.log("ID CWD") + runningInstances.forEach((inst, idx) => console.log(`${idx} ${inst.cwd}`)) +} + +async function handleStop(arg?: string) { + const brokerPort = await ensureBroker() + + const data = await fetchInstances(brokerPort) + if (!data) { + console.log("No instances running.") + return + } + + const instances = data.instances.filter((i) => i.status === "online") + + // -all / -a + if (arg === "-all" || arg === "-a") { + for (const inst of instances) await stopByInstanceCwd(brokerPort, inst.cwd) + return + } + + // No arg -> current directory + if (!arg) { + const cwd = path.resolve(process.cwd()) + await stopByInstanceCwd(brokerPort, cwd) + return + } + + // numeric id? + const idNum = Number(arg) + if (!Number.isNaN(idNum)) { + const inst = instances[idNum] + if (!inst) { + console.log(`No instance with id ${idNum}`) + process.exit(1) + } + await stopByInstanceCwd(brokerPort, inst.cwd) + return + } + + // treat as path + const absPath = path.resolve(arg) + await stopByInstanceCwd(brokerPort, absPath) +} + +async function handleStart(rawPath?: string) { + let projectDir = path.resolve(process.cwd(), rawPath ?? ".") + try { + const stat = fs.statSync(projectDir) + if (stat.isFile()) projectDir = path.dirname(projectDir) + } catch { + console.error(`Path does not exist: ${projectDir}`) + process.exit(1) + } + + const brokerPort = await ensureBroker() + + // call start endpoint + const res = await fetch(`http://${BROKER_HOST}:${brokerPort}/instance`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cwd: projectDir }), + }) + + if (res.status === 409) { + console.log(`Instance already running for ${projectDir}`) + return + } + + if (!res.ok) { + console.error(`Failed to start instance: ${await res.text()}`) + process.exit(1) + } + + console.log(`Starting instance for ${projectDir}...`) +} + +/************************* + * CLI definition via yargs + *************************/ + +yargs(hideBin(process.argv)) + .scriptName("opencode-web") + .command( + "$0 [path]", + "Start an instance for the given path (defaults to CWD)", + (y) => + y.positional("path", { + type: "string", + describe: "Directory or file to open", + default: ".", + }), + (args) => { + handleStart(args.path as string | undefined) + } + ) + .command( + "list", + "List running instances", + () => {}, + async () => { + await handleList() + } + ) + .command( + "stop [target]", + "Stop an instance by current dir, id, path, or -all/-a flag", + (y) => + y + .positional("target", { + type: "string", + describe: "id, path, or -all", + }) + .option("all", { + alias: "a", + type: "boolean", + describe: "Stop all instances", + }), + async (args) => { + if (args.all) await handleStop("-all") + else await handleStop(args.target as string | undefined) + } + ) + .help() + .version(false) + .strict() + .parse() diff --git a/cli/lib.js b/apps/cli/src/lib.ts similarity index 75% rename from cli/lib.js rename to apps/cli/src/lib.ts index 38140a0..a1f51fc 100644 --- a/cli/lib.js +++ b/apps/cli/src/lib.ts @@ -1,2 +1,3 @@ -export const BROKER_PORT_RANGE = [13943, 14839, 18503, 19304, 20197] +#!/usr/bin/env node +export const BROKER_PORT_RANGE = [13943, 14839, 18503, 19304, 20197] as const export const BROKER_HOST = "127.0.0.1" diff --git a/apps/cli/src/opencode-proc/index.ts b/apps/cli/src/opencode-proc/index.ts new file mode 100644 index 0000000..ad6748e --- /dev/null +++ b/apps/cli/src/opencode-proc/index.ts @@ -0,0 +1,144 @@ +#!/usr/bin/env node +import { spawn } from "child_process" +import path from "path" +import cors from "cors" +import express from "express" +import getPort from "get-port" +import { createProxyMiddleware } from "http-proxy-middleware" + +import { BROKER_HOST } from "../lib.js" +import { findBrokerPort } from "../utils.js" + +/************************* + * Helpers + *************************/ + +async function registerInstance( + brokerPort: number, + proxyPort: number, + cwd: string +) { + try { + await fetch(`http://${BROKER_HOST}:${brokerPort}/register`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cwd, port: proxyPort }), + }) + } catch (e) { + console.error("Failed to register with broker", e) + } +} + +async function pingInstance( + brokerPort: number, + proxyPort: number, + cwd: string +) { + try { + await fetch(`http://${BROKER_HOST}:${brokerPort}/ping`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cwd, port: proxyPort }), + }) + } catch {} +} + +async function deregisterInstance(brokerPort: number, cwd: string) { + try { + await fetch(`http://${BROKER_HOST}:${brokerPort}/deregister`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cwd }), + }) + } catch {} +} + +/************************* + * Main logic + *************************/ +const projectDir = path.resolve(process.argv[2] ?? process.cwd()) + +;(async () => { + const brokerPort = await findBrokerPort() + if (!brokerPort) { + console.error("Broker not running.") + process.exit(1) + } + console.log(`Broker running on port ${brokerPort}`) + + const proxyPort = await getPort({ port: 15096 }) + const opencodePort = await getPort({ port: 11923 }) + + console.log( + `Starting opencode server in ${projectDir} on port ${opencodePort}` + ) + const opencodeProc = spawn( + "opencode", + ["serve", "--port", `${opencodePort}`], + { + cwd: projectDir, + stdio: "inherit", + shell: true, + } + ) + + opencodeProc.on("error", (err) => { + console.error("Failed to start opencode server", err) + process.exit(1) + }) + + opencodeProc.on("exit", (code, signal) => { + if (code !== 0) { + console.error( + `opencode server exited with code ${code} (signal ${signal})` + ) + process.exit(code ?? 1) + } + }) + + const app = express() + app.use(cors()) + + // proxy API + app.use( + "/api", + createProxyMiddleware({ + target: `http://localhost:${opencodePort}`, + changeOrigin: true, + ws: true, + onProxyRes: (proxyRes: any) => { + proxyRes.headers["Access-Control-Allow-Origin"] = "*" + proxyRes.headers["Access-Control-Allow-Headers"] = "*" + proxyRes.headers["Access-Control-Allow-Methods"] = + "GET,POST,PUT,DELETE,OPTIONS" + }, + } as any) + ) + + // shutdown endpoint + const shutdown = async () => { + clearInterval(regInterval) + await deregisterInstance(brokerPort, projectDir) + server.close(() => process.exit(0)) + opencodeProc.kill() + } + + app.post("/__shutdown", async (_, res) => { + res.sendStatus(200) + await shutdown() + }) + + const server = app.listen(proxyPort, "127.0.0.1", () => { + console.log(`Local proxy on http://localhost:${proxyPort}`) + console.log("Open your web client: https://opencode-web.vercel.app") + }) + + await registerInstance(brokerPort, proxyPort, projectDir) + const regInterval = setInterval( + () => pingInstance(brokerPort, proxyPort, projectDir), + 10000 + ) + + process.on("SIGINT", shutdown) + process.on("SIGTERM", shutdown) +})() diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts new file mode 100644 index 0000000..e811a8c --- /dev/null +++ b/apps/cli/src/types.ts @@ -0,0 +1,8 @@ +export interface Instance { + cwd: string + port: number + host: string + status: "online" | "offline" + lastSeen?: number + startedAt: number +} diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts new file mode 100644 index 0000000..eb68cbd --- /dev/null +++ b/apps/cli/src/utils.ts @@ -0,0 +1,69 @@ +#!/usr/bin/env node +import { spawn } from "child_process" +import net from "net" +import path from "path" +import { fileURLToPath } from "url" + +import { BROKER_HOST, BROKER_PORT_RANGE } from "./lib" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +/************************* + * Spawn helpers + *************************/ +function delay(ms: number) { + return new Promise((r) => setTimeout(r, ms)) +} + +async function isBrokerOnPort(port: number): Promise { + try { + const res = await fetch(`http://${BROKER_HOST}:${port}/instances`) + if (!res.ok) return false + const data = (await res.json()) as any + return Array.isArray(data.instances) && data.info?.name === "opencode-web" + } catch { + return false + } +} + +// TODO: better way to find broker port +export async function findBrokerPort(): Promise { + for (const p of BROKER_PORT_RANGE) if (await isBrokerOnPort(p)) return p + return null +} + +function isPortFree(port: number): Promise { + return new Promise((resolve) => { + const srv = net.createServer() + srv.once("error", () => resolve(false)) + srv.once("listening", () => srv.close(() => resolve(true))) + srv.listen(port, BROKER_HOST) + }) +} + +async function spawnDetachedBroker(port: number): Promise { + const brokerPath = path.join(__dirname, "broker", "index.ts") + const proc = spawn("tsx", [brokerPath, `${port}`], { + cwd: path.dirname(brokerPath), + detached: true, + stdio: "ignore", + shell: true, + }) + proc.unref() + for (let i = 0; i < 20; i++) { + await delay(200) + if (await isBrokerOnPort(port)) return port + } + throw new Error(`Failed to start broker on port ${port}`) +} + +export async function ensureBroker(): Promise { + const existing = await findBrokerPort() + if (existing) return existing + // start one on free port in range + for (const p of BROKER_PORT_RANGE) { + if (await isPortFree(p)) return await spawnDetachedBroker(p) + } + throw new Error("Unable to start broker on any allowed port") +} diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json new file mode 100644 index 0000000..444cdb5 --- /dev/null +++ b/apps/cli/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@workspace/typescript-config/node", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "outDir": "dist", + "rootDir": "./", + }, + "include": ["**/*.ts"], + "exclude": ["dist"], + +} \ No newline at end of file diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/components.json b/apps/web/components.json similarity index 99% rename from components.json rename to apps/web/components.json index 13e1db0..73afbdb 100644 --- a/components.json +++ b/apps/web/components.json @@ -18,4 +18,4 @@ "hooks": "@/hooks" }, "iconLibrary": "lucide" -} +} \ No newline at end of file diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js new file mode 100644 index 0000000..07082ce --- /dev/null +++ b/apps/web/eslint.config.js @@ -0,0 +1,4 @@ +import reactConfig from "@workspace/eslint-config/react" + +/** @type {import("eslint").Linter.Config} */ +export default reactConfig diff --git a/index.html b/apps/web/index.html similarity index 100% rename from index.html rename to apps/web/index.html diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..7e1cda1 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,55 @@ +{ + "name": "web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@headegg-ai/sdk": "0.1.0-alpha.20", + "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", + "@tailwindcss/vite": "^4.1.11", + "@tanstack/react-query": "^5.83.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lang-map": "^0.4.0", + "lucide-react": "^0.525.0", + "nanoid": "^5.1.5", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.7.0", + "shiki": "^3.8.1", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.11", + "tw-animate-css": "^1.3.5", + "vite-tsconfig-paths": "^5.1.4", + "vscode-languageserver-types": "^3.17.5", + "zustand": "^5.0.6" + }, + "devDependencies": { + "@eslint/js": "^9.30.1", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "@workspace/eslint-config": "workspace:*", + "@workspace/typescript-config": "workspace:*", + "eslint": "^9.30.1", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "vite": "^7.0.4" + } +} diff --git a/public/vite.svg b/apps/web/public/vite.svg similarity index 100% rename from public/vite.svg rename to apps/web/public/vite.svg diff --git a/src/app.tsx b/apps/web/src/app.tsx similarity index 83% rename from src/app.tsx rename to apps/web/src/app.tsx index 920c3d1..60f8bdb 100644 --- a/src/app.tsx +++ b/apps/web/src/app.tsx @@ -2,6 +2,8 @@ import { ChatLayout } from "@/pages/chat" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { BrowserRouter, Route, Routes } from "react-router-dom" +import { OpencodeClientManager } from "@/components/opencode-client-manager" + export const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -14,9 +16,9 @@ export default function App() { return ( + } /> - } /> diff --git a/src/assets/react.svg b/apps/web/src/assets/react.svg similarity index 100% rename from src/assets/react.svg rename to apps/web/src/assets/react.svg diff --git a/src/components/chat-input.tsx b/apps/web/src/components/chat-input.tsx similarity index 100% rename from src/components/chat-input.tsx rename to apps/web/src/components/chat-input.tsx diff --git a/apps/web/src/components/content-code.tsx b/apps/web/src/components/content-code.tsx new file mode 100644 index 0000000..895c154 --- /dev/null +++ b/apps/web/src/components/content-code.tsx @@ -0,0 +1,42 @@ +import { useEffect, useState } from "react" +import { transformerNotationDiff } from "@shikijs/transformers" +import style from "@styles/content-code.module.css" +import { bundledLanguages, codeToHtml } from "shiki" + +interface Props { + code: string + lang?: string + flush?: boolean +} + +export function ContentCode(props: Props) { + const [html, setHtml] = useState("") + + useEffect(() => { + let cancelled = false + async function run() { + const result = await codeToHtml(props.code || "", { + lang: + props.lang && props.lang in bundledLanguages ? props.lang : "text", + themes: { + light: "github-light", + dark: "github-dark", + }, + transformers: [transformerNotationDiff()], + }) + if (!cancelled) setHtml(result as string) + } + run() + return () => { + cancelled = true + } + }, [props.code, props.lang]) + + return ( +
+ ) +} diff --git a/apps/web/src/components/content-diff.tsx b/apps/web/src/components/content-diff.tsx new file mode 100644 index 0000000..53faf7d --- /dev/null +++ b/apps/web/src/components/content-diff.tsx @@ -0,0 +1,212 @@ +import { useMemo } from "react" +import { parsePatch } from "diff" + +import { ContentCode } from "@/components/content-code" + +// import styles from "@/styles/content-diff.module.css" + +type DiffRow = { + left: string + right: string + type: "added" | "removed" | "unchanged" | "modified" +} + +interface Props { + diff: string + lang?: string +} + +export function ContentDiff(props: Props) { + const rows = useMemo(() => { + const diffRows: DiffRow[] = [] + + try { + const patches = parsePatch(props.diff) + + for (const patch of patches) { + for (const hunk of patch.hunks) { + const lines = hunk.lines + let i = 0 + + while (i < lines.length) { + const line = lines[i] + const content = line.slice(1) + const prefix = line[0] + + if (prefix === "-") { + // Look ahead for consecutive additions to pair with removals + const removals: string[] = [content] + let j = i + 1 + + // Collect all consecutive removals + while (j < lines.length && lines[j][0] === "-") { + removals.push(lines[j].slice(1)) + j++ + } + + // Collect all consecutive additions that follow + const additions: string[] = [] + while (j < lines.length && lines[j][0] === "+") { + additions.push(lines[j].slice(1)) + j++ + } + + // Pair removals with additions + const maxLength = Math.max(removals.length, additions.length) + for (let k = 0; k < maxLength; k++) { + const hasLeft = k < removals.length + const hasRight = k < additions.length + + if (hasLeft && hasRight) { + // Replacement - left is removed, right is added + diffRows.push({ + left: removals[k], + right: additions[k], + type: "modified", + }) + } else if (hasLeft) { + // Pure removal + diffRows.push({ + left: removals[k], + right: "", + type: "removed", + }) + } else if (hasRight) { + // Pure addition - only create if we actually have content + diffRows.push({ + left: "", + right: additions[k], + type: "added", + }) + } + } + + i = j + } else if (prefix === "+") { + // Standalone addition (not paired with removal) + diffRows.push({ + left: "", + right: content, + type: "added", + }) + i++ + } else if (prefix === " ") { + diffRows.push({ + left: content === "" ? " " : content, + right: content === "" ? " " : content, + type: "unchanged", + }) + i++ + } else { + i++ + } + } + } + } + } catch (error) { + console.error("Failed to parse patch:", error) + return [] + } + + return diffRows + }, []) + + const mobileRows = useMemo(() => { + const mobileBlocks: { + type: "removed" | "added" | "unchanged" + lines: string[] + }[] = [] + const currentRows = rows + + let i = 0 + while (i < currentRows.length) { + const removedLines: string[] = [] + const addedLines: string[] = [] + + // Collect consecutive modified/removed/added rows + while ( + i < currentRows.length && + (currentRows[i].type === "modified" || + currentRows[i].type === "removed" || + currentRows[i].type === "added") + ) { + const row = currentRows[i] + if (row.left && (row.type === "removed" || row.type === "modified")) { + removedLines.push(row.left) + } + if (row.right && (row.type === "added" || row.type === "modified")) { + addedLines.push(row.right) + } + i++ + } + + // Add grouped blocks + if (removedLines.length > 0) { + mobileBlocks.push({ type: "removed", lines: removedLines }) + } + if (addedLines.length > 0) { + mobileBlocks.push({ type: "added", lines: addedLines }) + } + + // Add unchanged rows as-is + if (i < currentRows.length && currentRows[i].type === "unchanged") { + mobileBlocks.push({ + type: "unchanged", + lines: [currentRows[i].left], + }) + i++ + } + } + + return mobileBlocks + }, [rows]) + + return ( +
+
+ {rows.map((r) => ( +
+
+ +
+
+ +
+
+ ))} +
+ +
+ {mobileRows.map((block) => ( +
+ {block.lines.map((line) => ( +
+ +
+ ))} +
+ ))} +
+
+ ) +} diff --git a/apps/web/src/components/copy-button.tsx b/apps/web/src/components/copy-button.tsx new file mode 100644 index 0000000..8499bd8 --- /dev/null +++ b/apps/web/src/components/copy-button.tsx @@ -0,0 +1,54 @@ +import { useState } from "react" +import { Check, Copy } from "lucide-react" + +import { copyToClipboard } from "@/lib/clipboard" +import { cn } from "@/lib/utils" + +import { Button } from "@/components/ui/button" + +interface CopyButtonProps { + text: string + className?: string + variant?: "default" | "outline" | "ghost" + size?: "default" | "sm" | "lg" + children?: React.ReactNode +} + +export function CopyButton({ + text, + className, + variant = "outline", + size = "default", + children, +}: CopyButtonProps) { + const [copied, setCopied] = useState(false) + + const handleCopy = async () => { + const success = await copyToClipboard(text) + if (success) { + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + } + + return ( + + ) +} diff --git a/apps/web/src/components/instance-switcher.tsx b/apps/web/src/components/instance-switcher.tsx new file mode 100644 index 0000000..a9bacc3 --- /dev/null +++ b/apps/web/src/components/instance-switcher.tsx @@ -0,0 +1,308 @@ +"use client" + +import { useMemo, useState } from "react" +import { useLastSessionStore } from "@/store/last-session" +import { useRecentProjectsStore } from "@/store/recent-projects" +import { ChevronsUpDown, GalleryVerticalEnd, Plus, Square } from "lucide-react" +import { useSearchParams } from "react-router-dom" + +import { cn } from "@/lib/utils" +import { useGetInstances } from "@/hooks/fetch/broker" +import { useStartInstance, useStopInstance } from "@/hooks/fetch/instances" +import { useOpencodeClient } from "@/hooks/use-opencode-client" +import { useUrlParams } from "@/hooks/use-url-params" + +import { Button } from "@/components/ui/button" +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { TerminalCommand } from "@/components/terminal-command" + +export function InstanceSwitcher() { + // Move ALL hooks to the top before any conditional logic + const [open, setOpen] = useState(false) + const [dialogOpen, setDialogOpen] = useState(false) + + const { data: brokerData, isLoading } = useGetInstances() + const stopInstanceMutation = useStopInstance() + const startInstanceMutation = useStartInstance() + + // NEW: Use our opencode client hook + const { cwd: currentCwd } = useUrlParams() + const [, setSearchParams] = useSearchParams() + const getLastSession = useLastSessionStore((s) => s.getLastSession) + const removeLastSession = useLastSessionStore((s) => s.removeLastSession) + // Access recent projects (stored locally) + const { projects } = useRecentProjectsStore() + + const instances = useMemo(() => { + // Combine broker-reported instances with locally stored recent projects. + const brokerInstances = brokerData?.instances ?? [] + + // Map to de-duplicate when the same path appears twice. + const map = new Map() + + // 1) Add all broker instances first (both online & offline) + brokerInstances.forEach((inst) => { + map.set(inst.cwd, inst) + }) + + // 2) Add recent projects not already present (mark as offline) + projects.forEach((proj) => { + if (!map.has(proj.path)) { + map.set(proj.path, { + cwd: proj.path, + host: "", + port: 0, + status: "offline", + lastSeen: new Date(proj.lastUsed).getTime(), + // Use 0 so they naturally sort after any running instances + startedAt: 0, + }) + } + }) + + const list = Array.from(map.values()) + + // Helper: sort by startedAt (oldest first; 0/undefined treated as large) + const byStartedAsc = ( + a: (typeof list)[number], + b: (typeof list)[number] + ) => { + const aTime = a.startedAt ?? Number.MAX_SAFE_INTEGER + const bTime = b.startedAt ?? Number.MAX_SAFE_INTEGER + return aTime - bTime + } + + const online = list.filter((i) => i.status === "online").sort(byStartedAsc) + const offline = list.filter((i) => i.status !== "online").sort(byStartedAsc) + + return [...online, ...offline] + }, [brokerData, projects]) + + // Determine selected instance by currentCwd (fallback to first) - BEFORE any returns + const selectedInstance = useMemo(() => { + return instances.find((i) => i.cwd === currentCwd) || instances[0] + }, [instances, currentCwd]) + + // Derive a human-friendly label (project folder name) - BEFORE any returns + const label = useMemo(() => { + if (!selectedInstance) return "" + const parts = selectedInstance.cwd.split(/[/\\]/) + return parts[parts.length - 1] || selectedInstance.cwd + }, [selectedInstance]) + + // Helper to change URL cwd param + const selectInstance = (cwd: string, restore: boolean = true) => { + const last = restore ? getLastSession(cwd) : undefined + setSearchParams( + (prev) => { + const next = new URLSearchParams(prev) + next.set("cwd", cwd) + if (last) { + next.set("session", last) + } else { + next.delete("session") + } + return next + }, + { replace: true } + ) + } + + // NOW we can do conditional returns after all hooks are called + if (isLoading) return null + + if (!instances.length) { + return ( + + + + No codebase added + + + + ) + } + + return ( + + + + + + {label} + + + + + + + + + + + Add codebase + + + + + Add New Codebase + +
+
+

+ Running the opencode-web CLI connects to the codebase in + the current directory. +

+ +
+ +
+

+ Or you can connect to a codebase in a specific + directory. +

+ +
+
+
+
+
+ + {instances.map((instance) => { + const instanceLabel = (() => { + const parts = instance.cwd.split(/[/\\]/) + return parts[parts.length - 1] || instance.cwd + })() + + const isSelected = instance.cwd === currentCwd + + return ( + +
+ { + if (instance.status === "online") { + selectInstance(instance.cwd) + } else { + startInstanceMutation.mutate(instance.cwd, { + onSuccess: () => + selectInstance(instance.cwd, false), + }) + } + }} + className={cn( + "w-full h-14 px-4 transition-colors", + isSelected && "bg-accent text-accent-foreground" + )} + > +
+ {/* status dot */} + + +
+
+ {instanceLabel} +
+
+ {instance.cwd} +
+
+
+
+ + {/* Hover stop button */} +
+ + + {instance.status === "online" ? ( + + ) : ( + + )} + + +

+ {instance.status === "online" + ? "Exit codebase" + : "Start codebase"} +

+
+
+
+
+
+ ) + })} +
+
+
+
+ ) +} diff --git a/apps/web/src/components/opencode-client-manager.tsx b/apps/web/src/components/opencode-client-manager.tsx new file mode 100644 index 0000000..0895969 --- /dev/null +++ b/apps/web/src/components/opencode-client-manager.tsx @@ -0,0 +1,102 @@ +import { useEffect, useMemo } from "react" +import { useLastSessionStore } from "@/store/last-session" +import { useOpencodeClientStore } from "@/store/opencode-client" +import { useSearchParams } from "react-router-dom" + +import { useGetInstances } from "@/hooks/fetch/broker" +import { useUrlParams } from "@/hooks/use-url-params" + +/** + * Manager component that syncs broker data with Opencode client store + * Place this once at the app root - it has no UI, just side effects + */ +export function OpencodeClientManager() { + const { data: brokerData } = useGetInstances() + const { cwd: cwdFromUrl } = useUrlParams() + const { createClient, removeClient, clearAllClients } = + useOpencodeClientStore() + const removeLastSession = useLastSessionStore((s) => s.removeLastSession) + const [searchParams, setSearchParams] = useSearchParams() + + const instances = brokerData?.instances || [] + const onlineInstances = instances.filter((inst) => inst.status === "online") + + // Determine what the current cwd should be + const currentCwd = useMemo(() => { + // If URL has cwd, use that (even if offline) + if (cwdFromUrl) { + return cwdFromUrl + } + + // No URL cwd - default to first online instance + if (onlineInstances.length > 0) { + return onlineInstances[0].cwd + } + + return null + }, [cwdFromUrl, onlineInstances]) + + // Create clients for all online instances + useEffect(() => { + onlineInstances.forEach((instance) => { + createClient(instance.cwd, instance.port) + }) + }, [onlineInstances, createClient]) + + // If the cwd in URL is no longer online, switch to first online or remove param + useEffect(() => { + if (!cwdFromUrl) return + + const isCurrentOnline = onlineInstances.some( + (inst) => inst.cwd === cwdFromUrl && inst.status === "online" + ) + + if (isCurrentOnline) return // still valid + + const newParams = new URLSearchParams(searchParams) + + if (onlineInstances.length > 0) { + // Switch to first online instance + newParams.set("cwd", onlineInstances[0].cwd) + } else { + // No online instances, remove the param + newParams.delete("cwd") + newParams.delete("sessionId") + } + + setSearchParams(newParams, { replace: true }) + }, [cwdFromUrl, onlineInstances, searchParams, setSearchParams]) + + // Cleanup clients for instances that are no longer available + useEffect(() => { + const availableCwds = new Set(instances.map((inst) => inst.cwd)) + const { clients } = useOpencodeClientStore.getState() + + // Remove clients for cwds that no longer exist + Object.keys(clients).forEach((cwd) => { + if (!availableCwds.has(cwd)) { + removeClient(cwd) + removeLastSession(cwd) + } + }) + }, [instances, removeClient, removeLastSession]) + + // Clear all clients when broker is offline + useEffect(() => { + if (brokerData?.brokerStatus === "offline") { + clearAllClients() + } + }, [brokerData?.brokerStatus, clearAllClients]) + + // If no cwd param but we have a currentCwd (default), push it to URL + useEffect(() => { + if (!cwdFromUrl && currentCwd) { + const newParams = new URLSearchParams(searchParams) + newParams.set("cwd", currentCwd) + setSearchParams(newParams, { replace: true }) + } + }, [cwdFromUrl, currentCwd, searchParams, setSearchParams]) + + // This component renders nothing + return null +} diff --git a/src/components/search-form.tsx b/apps/web/src/components/search-form.tsx similarity index 100% rename from src/components/search-form.tsx rename to apps/web/src/components/search-form.tsx diff --git a/apps/web/src/components/terminal-command.tsx b/apps/web/src/components/terminal-command.tsx new file mode 100644 index 0000000..a887daa --- /dev/null +++ b/apps/web/src/components/terminal-command.tsx @@ -0,0 +1,20 @@ +import { Terminal } from "lucide-react" + +import { CopyButton } from "@/components/copy-button" + +interface TerminalCommandProps { + command: string + className?: string +} + +export function TerminalCommand({ command, className }: TerminalCommandProps) { + return ( +
+ + {command} + +
+ ) +} diff --git a/src/components/ui/breadcrumb.tsx b/apps/web/src/components/ui/breadcrumb.tsx similarity index 100% rename from src/components/ui/breadcrumb.tsx rename to apps/web/src/components/ui/breadcrumb.tsx diff --git a/src/components/ui/button-variants.ts b/apps/web/src/components/ui/button-variants.ts similarity index 100% rename from src/components/ui/button-variants.ts rename to apps/web/src/components/ui/button-variants.ts diff --git a/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx similarity index 100% rename from src/components/ui/button.tsx rename to apps/web/src/components/ui/button.tsx diff --git a/src/components/ui/card.tsx b/apps/web/src/components/ui/card.tsx similarity index 100% rename from src/components/ui/card.tsx rename to apps/web/src/components/ui/card.tsx diff --git a/apps/web/src/components/ui/collapsible.tsx b/apps/web/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..77f86be --- /dev/null +++ b/apps/web/src/components/ui/collapsible.tsx @@ -0,0 +1,31 @@ +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +function Collapsible({ + ...props +}: React.ComponentProps) { + return +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx similarity index 100% rename from src/components/ui/command.tsx rename to apps/web/src/components/ui/command.tsx diff --git a/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx similarity index 100% rename from src/components/ui/dialog.tsx rename to apps/web/src/components/ui/dialog.tsx diff --git a/src/components/ui/dropdown-menu.tsx b/apps/web/src/components/ui/dropdown-menu.tsx similarity index 100% rename from src/components/ui/dropdown-menu.tsx rename to apps/web/src/components/ui/dropdown-menu.tsx diff --git a/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx similarity index 100% rename from src/components/ui/input.tsx rename to apps/web/src/components/ui/input.tsx diff --git a/src/components/ui/label.tsx b/apps/web/src/components/ui/label.tsx similarity index 100% rename from src/components/ui/label.tsx rename to apps/web/src/components/ui/label.tsx diff --git a/src/components/ui/popover.tsx b/apps/web/src/components/ui/popover.tsx similarity index 100% rename from src/components/ui/popover.tsx rename to apps/web/src/components/ui/popover.tsx diff --git a/src/components/ui/select.tsx b/apps/web/src/components/ui/select.tsx similarity index 100% rename from src/components/ui/select.tsx rename to apps/web/src/components/ui/select.tsx diff --git a/src/components/ui/separator.tsx b/apps/web/src/components/ui/separator.tsx similarity index 100% rename from src/components/ui/separator.tsx rename to apps/web/src/components/ui/separator.tsx diff --git a/src/components/ui/sheet.tsx b/apps/web/src/components/ui/sheet.tsx similarity index 100% rename from src/components/ui/sheet.tsx rename to apps/web/src/components/ui/sheet.tsx diff --git a/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx similarity index 100% rename from src/components/ui/sidebar.tsx rename to apps/web/src/components/ui/sidebar.tsx diff --git a/src/components/ui/skeleton.tsx b/apps/web/src/components/ui/skeleton.tsx similarity index 100% rename from src/components/ui/skeleton.tsx rename to apps/web/src/components/ui/skeleton.tsx diff --git a/src/components/ui/textarea.tsx b/apps/web/src/components/ui/textarea.tsx similarity index 100% rename from src/components/ui/textarea.tsx rename to apps/web/src/components/ui/textarea.tsx diff --git a/src/components/ui/tooltip.tsx b/apps/web/src/components/ui/tooltip.tsx similarity index 100% rename from src/components/ui/tooltip.tsx rename to apps/web/src/components/ui/tooltip.tsx diff --git a/src/hooks/fetch/app.tsx b/apps/web/src/hooks/fetch/app.tsx similarity index 54% rename from src/hooks/fetch/app.tsx rename to apps/web/src/hooks/fetch/app.tsx index e087a69..5aedd10 100644 --- a/src/hooks/fetch/app.tsx +++ b/apps/web/src/hooks/fetch/app.tsx @@ -1,13 +1,18 @@ import type { Opencode } from "@opencode-ai/sdk" import { useQuery } from "@tanstack/react-query" -import { opencodeClient } from "@/lib/opencode-client" +import { useOpencodeClient } from "@/hooks/use-opencode-client" +import { useUrlParams } from "@/hooks/use-url-params" -export const useGetAppInfo = () => - useQuery({ - queryKey: ["appInfo"], +export const useGetAppInfo = () => { + const opencodeClient = useOpencodeClient() + const { cwd } = useUrlParams() + + return useQuery({ + queryKey: ["appInfo", { cwd }], + enabled: !!opencodeClient && !!cwd, queryFn: () => - opencodeClient.app.get().then((data) => { + opencodeClient!.app.get().then((data) => { // Cross-platform: get last segment of path.root const root = data.path.root let projectName = "" @@ -20,3 +25,4 @@ export const useGetAppInfo = () => return { ...data, projectName } }), }) +} diff --git a/apps/web/src/hooks/fetch/broker.tsx b/apps/web/src/hooks/fetch/broker.tsx new file mode 100644 index 0000000..a5246cc --- /dev/null +++ b/apps/web/src/hooks/fetch/broker.tsx @@ -0,0 +1,66 @@ +import { useQuery } from "@tanstack/react-query" + +// TODO: These constants should stay in sync with the CLI +const BROKER_PORT_RANGE = [13943, 14839, 18503, 19304, 20197] as const +const BROKER_HOST = "127.0.0.1" + +export interface Instance { + cwd: string + port: number + host: string + status: "online" | "offline" + lastSeen?: number + startedAt: number +} + +export interface InstancesResponse { + version: string + info: { name: string } + instances: Instance[] +} + +export interface BrokerResponse { + brokerStatus: "online" | "offline" + instances: Instance[] +} + +async function fetchInstances(): Promise { + const requests = BROKER_PORT_RANGE.map(async (port) => { + try { + const res = await fetch(`http://${BROKER_HOST}:${port}/instances`, { + // Ensure we don\'t keep aborted connections around + cache: "no-store", + }) + if (!res.ok) throw new Error("Request failed") + const data = (await res.json()) as InstancesResponse + // Basic validation + if (Array.isArray(data.instances)) { + return data.instances + } + throw new Error("Invalid response format") + } catch { + // Throw error to be caught by Promise.any + throw new Error("Request failed") + } + }) + + try { + const instances = await Promise.any(requests) + return { + brokerStatus: "online", + instances, + } + } catch { + // Broker not found on any port + return { + brokerStatus: "offline", + instances: [], + } + } +} + +export const useGetInstances = () => + useQuery({ + queryKey: ["instances"], + queryFn: fetchInstances, + }) diff --git a/apps/web/src/hooks/fetch/instances.tsx b/apps/web/src/hooks/fetch/instances.tsx new file mode 100644 index 0000000..e7c8b47 --- /dev/null +++ b/apps/web/src/hooks/fetch/instances.tsx @@ -0,0 +1,111 @@ +import { useRecentProjectsStore } from "@/store/recent-projects" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useSearchParams } from "react-router-dom" + +import { useGetInstances } from "./broker" + +// TODO: These constants should stay in sync with the CLI +const BROKER_PORT_RANGE = [13943, 14839, 18503, 19304, 20197] as const +const BROKER_HOST = "127.0.0.1" + +async function startInstance( + projectPath: string +): Promise<{ message: string; port: number }> { + for (const port of BROKER_PORT_RANGE) { + try { + const res = await fetch(`http://${BROKER_HOST}:${port}/instance`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cwd: projectPath }), + }) + + if (res.status === 409) { + throw new Error("Instance already running for this project") + } + + if (!res.ok) { + const errorText = await res.text() + throw new Error(`Failed to start instance: ${errorText}`) + } + + return await res.json() + } catch (error) { + if (error instanceof Error && error.message.includes("already running")) { + throw error + } + // Continue to next port + } + } + throw new Error("Broker not found on any known port") +} + +async function stopInstance(projectPath: string): Promise<{ message: string }> { + for (const port of BROKER_PORT_RANGE) { + try { + const res = await fetch(`http://${BROKER_HOST}:${port}/instance`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cwd: projectPath }), + }) + + if (res.status === 404) { + throw new Error("Instance not found") + } + + if (!res.ok) { + const errorText = await res.text() + throw new Error(`Failed to stop instance: ${errorText}`) + } + + return await res.json() + } catch (error) { + if (error instanceof Error && error.message.includes("not found")) { + throw error + } + // Continue to next port + } + } + throw new Error("Broker not found on any known port") +} + +export function useStartInstance() { + const queryClient = useQueryClient() + const { addProject } = useRecentProjectsStore() + const [, setSearchParams] = useSearchParams() + + return useMutation({ + mutationFn: startInstance, + onSuccess: (_, projectPath) => { + // Add to recent projects + addProject({ + path: projectPath, + name: projectPath.split(/[/\\]/).pop() || projectPath, + }) + + // Invalidate instances query to refresh the list + queryClient.invalidateQueries({ queryKey: ["instances"] }) + + // Update URL param to make the newly started instance active using React Router + setSearchParams( + (prev) => { + const next = new URLSearchParams(prev) + next.set("cwd", projectPath) + return next + }, + { replace: true } + ) + }, + }) +} + +export function useStopInstance() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: stopInstance, + onSuccess: () => { + // Invalidate instances query to refresh the list + queryClient.invalidateQueries({ queryKey: ["instances"] }) + }, + }) +} diff --git a/src/hooks/fetch/messages.tsx b/apps/web/src/hooks/fetch/messages.tsx similarity index 55% rename from src/hooks/fetch/messages.tsx rename to apps/web/src/hooks/fetch/messages.tsx index d95a51b..4b3bb4f 100644 --- a/src/hooks/fetch/messages.tsx +++ b/apps/web/src/hooks/fetch/messages.tsx @@ -1,24 +1,25 @@ import type { Opencode } from "@opencode-ai/sdk" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" -import { opencodeClient } from "@/lib/opencode-client" +import { useOpencodeClient } from "@/hooks/use-opencode-client" export const useGetMessages = ({ sessionId, }: { sessionId: string | undefined -}) => - useQuery({ +}) => { + const opencodeClient = useOpencodeClient() + + return useQuery({ queryKey: ["messages", sessionId], - queryFn: () => { - if (!sessionId) throw new Error("Session ID is required") - return opencodeClient.session.messages(sessionId) - }, - enabled: !!sessionId, + enabled: !!sessionId && !!opencodeClient, + queryFn: () => opencodeClient!.session.messages(sessionId!), }) +} export const useSendMessage = () => { const queryClient = useQueryClient() + const opencodeClient = useOpencodeClient() return useMutation< Opencode.AssistantMessage, @@ -28,8 +29,10 @@ export const useSendMessage = () => { payload: Opencode.SessionChatParams } >({ - mutationFn: ({ sessionId, payload }) => - opencodeClient.session.chat(sessionId, payload), + mutationFn: ({ sessionId, payload }) => { + if (!opencodeClient) throw new Error("Opencode client not available") + return opencodeClient.session.chat(sessionId, payload) + }, onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ["messages", variables.sessionId], diff --git a/src/hooks/fetch/providers.tsx b/apps/web/src/hooks/fetch/providers.tsx similarity index 100% rename from src/hooks/fetch/providers.tsx rename to apps/web/src/hooks/fetch/providers.tsx diff --git a/src/hooks/fetch/sessions.tsx b/apps/web/src/hooks/fetch/sessions.tsx similarity index 53% rename from src/hooks/fetch/sessions.tsx rename to apps/web/src/hooks/fetch/sessions.tsx index 1118beb..6c45273 100644 --- a/src/hooks/fetch/sessions.tsx +++ b/apps/web/src/hooks/fetch/sessions.tsx @@ -1,23 +1,28 @@ import { useMemo } from "react" import type { Opencode } from "@opencode-ai/sdk" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" -import { useParams } from "react-router-dom" -import { opencodeClient } from "@/lib/opencode-client" +import { useOpencodeClient } from "@/hooks/use-opencode-client" +import { useUrlParams } from "@/hooks/use-url-params" -export const useGetSessions = () => - useQuery({ - queryKey: ["sessions"], +export const useGetSessions = () => { + const opencodeClient = useOpencodeClient() + const { cwd } = useUrlParams() + + return useQuery({ + queryKey: ["sessions", { cwd }], + enabled: !!opencodeClient && !!cwd, queryFn: () => - opencodeClient.session.list().then((data) => { + opencodeClient!.session.list().then((data) => { // Sort by creation time (newest first) data.sort((a, b) => (b.time?.created ?? 0) - (a.time?.created ?? 0)) return data }), }) +} export const useGetActiveSession = () => { - const { sessionId } = useParams<{ sessionId: string }>() + const { sessionId } = useUrlParams() const states = useGetSessions() const activeSession = useMemo(() => { @@ -34,11 +39,16 @@ export const useGetActiveSession = () => { */ export const useCreateSession = () => { const queryClient = useQueryClient() + const opencodeClient = useOpencodeClient() + const { cwd } = useUrlParams() return useMutation({ - mutationFn: () => opencodeClient.session.create(), + mutationFn: () => { + if (!opencodeClient) throw new Error("Opencode client not available") + return opencodeClient.session.create() + }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["sessions"] }) + queryClient.invalidateQueries({ queryKey: ["sessions", { cwd }] }) }, }) } @@ -49,11 +59,16 @@ export const useCreateSession = () => { */ export const useDeleteSession = () => { const queryClient = useQueryClient() + const opencodeClient = useOpencodeClient() + const { cwd } = useUrlParams() return useMutation({ - mutationFn: (id: string) => opencodeClient.session.delete(id), + mutationFn: (id: string) => { + if (!opencodeClient) throw new Error("Opencode client not available") + return opencodeClient.session.delete(id) + }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["sessions"] }) + queryClient.invalidateQueries({ queryKey: ["sessions", { cwd }] }) }, }) } diff --git a/src/hooks/use-handle-session-message-events.ts b/apps/web/src/hooks/use-handle-session-message-events.ts similarity index 93% rename from src/hooks/use-handle-session-message-events.ts rename to apps/web/src/hooks/use-handle-session-message-events.ts index 769f079..4282450 100644 --- a/src/hooks/use-handle-session-message-events.ts +++ b/apps/web/src/hooks/use-handle-session-message-events.ts @@ -5,8 +5,8 @@ import type { Opencode } from "@opencode-ai/sdk" import { Stream } from "@opencode-ai/sdk/core/streaming" import { useQueryClient } from "@tanstack/react-query" -import { opencodeClient } from "@/lib/opencode-client" import { useGetActiveSession } from "@/hooks/fetch/sessions" +import { useOpencodeClient } from "@/hooks/use-opencode-client" const handleMessageUpdated = (eventData: Opencode.EventListResponse) => { if (eventData.type !== "message.updated") return @@ -83,13 +83,14 @@ const handleMessagePartUpdated = (eventData: Opencode.EventListResponse) => { export function useHandleSessionMessageEvents() { const queryClient = useQueryClient() const { data: activeSession } = useGetActiveSession() + const opencodeClient = useOpencodeClient() const streamRef = useRef | null>(null) // Streaming: Listen to /event for message updates const activeSessionId = activeSession?.id useEffect(() => { - if (!activeSessionId) return + if (!activeSessionId || !opencodeClient) return // TODO: handle null client gracefully let isActive = true @@ -127,5 +128,5 @@ export function useHandleSessionMessageEvents() { streamRef.current = null } } - }, [activeSessionId, queryClient]) + }, [activeSessionId, opencodeClient, queryClient]) } diff --git a/src/hooks/use-mobile.ts b/apps/web/src/hooks/use-mobile.ts similarity index 100% rename from src/hooks/use-mobile.ts rename to apps/web/src/hooks/use-mobile.ts diff --git a/apps/web/src/hooks/use-opencode-client.ts b/apps/web/src/hooks/use-opencode-client.ts new file mode 100644 index 0000000..66bacff --- /dev/null +++ b/apps/web/src/hooks/use-opencode-client.ts @@ -0,0 +1,14 @@ +import { useOpencodeClientStore } from "@/store/opencode-client" + +import { useUrlParams } from "@/hooks/use-url-params" + +/** + * Simple hook to get the current Opencode client based on URL cwd parameter + * Returns the client instance or null if no client exists for current cwd + */ +export function useOpencodeClient() { + const { cwd } = useUrlParams() + const getClient = useOpencodeClientStore((state) => state.getClient) + + return cwd ? getClient(cwd) : null +} diff --git a/apps/web/src/hooks/use-url-params.ts b/apps/web/src/hooks/use-url-params.ts new file mode 100644 index 0000000..b3e5957 --- /dev/null +++ b/apps/web/src/hooks/use-url-params.ts @@ -0,0 +1,15 @@ +import { useSearchParams } from "react-router-dom" + +export interface UrlParams { + cwd: string | null + sessionId: string | null +} + +export function useUrlParams(): UrlParams { + const [searchParams] = useSearchParams() + + return { + cwd: searchParams.get("cwd"), + sessionId: searchParams.get("session"), + } +} diff --git a/src/index.css b/apps/web/src/index.css similarity index 66% rename from src/index.css rename to apps/web/src/index.css index 64a15bd..93e5c8d 100644 --- a/src/index.css +++ b/apps/web/src/index.css @@ -59,41 +59,41 @@ :root { --background: oklch(1 0 0); - --foreground: oklch(0.1448 0 0); + --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); - --card-foreground: oklch(0.1448 0 0); + --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); - --popover-foreground: oklch(0.1448 0 0); - --primary: oklch(0.5555 0 0); - --primary-foreground: oklch(0.9851 0 0); - --secondary: oklch(0.9702 0 0); - --secondary-foreground: oklch(0.2046 0 0); - --muted: oklch(0.9702 0 0); - --muted-foreground: oklch(0.5486 0 0); - --accent: oklch(0.9702 0 0); - --accent-foreground: oklch(0.2046 0 0); - --destructive: oklch(0.583 0.2387 28.4765); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.9702 0 0); - --border: oklch(0.9219 0 0); - --input: oklch(0.9219 0 0); - --ring: oklch(0.709 0 0); - --chart-1: oklch(0.5555 0 0); - --chart-2: oklch(0.5555 0 0); - --chart-3: oklch(0.5555 0 0); - --chart-4: oklch(0.5555 0 0); - --chart-5: oklch(0.5555 0 0); - --sidebar: oklch(1 0 0); - --sidebar-foreground: oklch(0.1448 0 0); - --sidebar-primary: oklch(0.2046 0 0); - --sidebar-primary-foreground: oklch(0.9851 0 0); - --sidebar-accent: oklch(0.9702 0 0); - --sidebar-accent-foreground: oklch(0.2046 0 0); - --sidebar-border: oklch(0.9219 0 0); - --sidebar-ring: oklch(0.709 0 0); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); --font-sans: Geist Mono, monospace; --font-serif: Geist Mono, monospace; --font-mono: Geist Mono, monospace; - --radius: 0rem; + --radius: 0.625rem; --shadow-2xs: 0px 1px 0px 0px hsl(0 0% 0% / 0); --shadow-xs: 0px 1px 0px 0px hsl(0 0% 0% / 0); --shadow-sm: @@ -111,38 +111,38 @@ } .dark { - --background: oklch(0.1448 0 0); - --foreground: oklch(0.9851 0 0); - --card: oklch(0.2134 0 0); - --card-foreground: oklch(0.9851 0 0); - --popover: oklch(0.2686 0 0); - --popover-foreground: oklch(0.9851 0 0); - --primary: oklch(0.5555 0 0); - --primary-foreground: oklch(0.9851 0 0); - --secondary: oklch(0.2686 0 0); - --secondary-foreground: oklch(0.9851 0 0); - --muted: oklch(0.2686 0 0); - --muted-foreground: oklch(0.709 0 0); - --accent: oklch(0.3715 0 0); - --accent-foreground: oklch(0.9851 0 0); - --destructive: oklch(0.7022 0.1892 22.2279); + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); --destructive-foreground: oklch(0.2686 0 0); - --border: oklch(0.3407 0 0); - --input: oklch(0.4386 0 0); - --ring: oklch(0.5555 0 0); - --chart-1: oklch(0.5555 0 0); - --chart-2: oklch(0.5555 0 0); - --chart-3: oklch(0.5555 0 0); - --chart-4: oklch(0.5555 0 0); - --chart-5: oklch(0.5555 0 0); - --sidebar: oklch(0.1448 0 0); - --sidebar-foreground: oklch(0.9851 0 0); - --sidebar-primary: oklch(0.9851 0 0); - --sidebar-primary-foreground: oklch(0.2046 0 0); - --sidebar-accent: oklch(0.2686 0 0); - --sidebar-accent-foreground: oklch(0.9851 0 0); - --sidebar-border: oklch(1 0 0); - --sidebar-ring: oklch(0.4386 0 0); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); --font-sans: Geist Mono, monospace; --font-serif: Geist Mono, monospace; --font-mono: Geist Mono, monospace; diff --git a/apps/web/src/lib/clipboard.ts b/apps/web/src/lib/clipboard.ts new file mode 100644 index 0000000..163c3e0 --- /dev/null +++ b/apps/web/src/lib/clipboard.ts @@ -0,0 +1,25 @@ +export async function copyToClipboard(text: string): Promise { + try { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text) + return true + } else { + // Fallback for older browsers or non-secure contexts + const textArea = document.createElement("textarea") + textArea.value = text + textArea.style.position = "fixed" + textArea.style.left = "-999999px" + textArea.style.top = "-999999px" + document.body.appendChild(textArea) + textArea.focus() + textArea.select() + + const result = document.execCommand("copy") + document.body.removeChild(textArea) + return result + } + } catch (error) { + console.error("Failed to copy to clipboard:", error) + return false + } +} diff --git a/src/lib/generateId.ts b/apps/web/src/lib/generateId.ts similarity index 100% rename from src/lib/generateId.ts rename to apps/web/src/lib/generateId.ts diff --git a/src/lib/opencode-client.ts b/apps/web/src/lib/opencode-client.ts similarity index 100% rename from src/lib/opencode-client.ts rename to apps/web/src/lib/opencode-client.ts diff --git a/src/lib/use-stick-to-bottom/component.tsx b/apps/web/src/lib/use-stick-to-bottom/component.tsx similarity index 100% rename from src/lib/use-stick-to-bottom/component.tsx rename to apps/web/src/lib/use-stick-to-bottom/component.tsx diff --git a/src/lib/use-stick-to-bottom/hook.ts b/apps/web/src/lib/use-stick-to-bottom/hook.ts similarity index 100% rename from src/lib/use-stick-to-bottom/hook.ts rename to apps/web/src/lib/use-stick-to-bottom/hook.ts diff --git a/src/lib/use-stick-to-bottom/index.ts b/apps/web/src/lib/use-stick-to-bottom/index.ts similarity index 100% rename from src/lib/use-stick-to-bottom/index.ts rename to apps/web/src/lib/use-stick-to-bottom/index.ts diff --git a/src/lib/utils.ts b/apps/web/src/lib/utils.ts similarity index 100% rename from src/lib/utils.ts rename to apps/web/src/lib/utils.ts diff --git a/src/main.tsx b/apps/web/src/main.tsx similarity index 100% rename from src/main.tsx rename to apps/web/src/main.tsx diff --git a/apps/web/src/pages/chat/components/chat-input.tsx b/apps/web/src/pages/chat/components/chat-input.tsx new file mode 100644 index 0000000..0d14010 --- /dev/null +++ b/apps/web/src/pages/chat/components/chat-input.tsx @@ -0,0 +1,155 @@ +import { useState } from "react" +import { ProviderSelect } from "@/pages/chat/components/provider-select" +import { useSelectedModelStore } from "@/store" +import { useLastSessionStore } from "@/store/last-session" +import type { MessageWithParts } from "@/types" +import type { Opencode } from "@opencode-ai/sdk" +import { useQueryClient } from "@tanstack/react-query" +import { useSearchParams } from "react-router-dom" + +import { generateNewID, ID } from "@/lib/generateId" +import { useGetMessages, useSendMessage } from "@/hooks/fetch/messages" +import { useCreateSession, useGetActiveSession } from "@/hooks/fetch/sessions" +import { useUrlParams } from "@/hooks/use-url-params" + +import { ChatInputSubmit, ChatInputTextarea } from "@/components/chat-input" + +export function ChatInput() { + const { data: activeSession } = useGetActiveSession() + const createSessionMutation = useCreateSession() + const [, setSearchParams] = useSearchParams() + const { cwd } = useUrlParams() + const setLastSession = useLastSessionStore((s) => s.setLastSession) + + const { data: messages } = useGetMessages({ sessionId: activeSession?.id }) + + const [input, setInput] = useState("") + + const sendMessageMutation = useSendMessage() + const selectedModel = useSelectedModelStore((s) => s.selectedModel) + + const queryClient = useQueryClient() + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!input.trim() || !selectedModel) return + + // Helper to actually send the message once we have a session ID + const doSend = (sessionId: string) => { + const messageID = generateNewID(ID.MESSAGE) + + // add a temp user message to the messages array + const newMessagePart: Opencode.TextPart = { + id: generateNewID(ID.PART), + sessionID: sessionId, + messageID, + type: "text", + text: input, + } + + const payload: Opencode.SessionChatParams = { + messageID, + providerID: selectedModel.providerID, + modelID: selectedModel.modelID, + mode: "build", + parts: [newMessagePart], + } + + const enteredInput = input + setInput("") + + const optimisticNewMessageWithParts: MessageWithParts = { + info: { + role: "user", + sessionID: sessionId, + time: { + created: Date.now(), + }, + id: messageID, + }, + parts: [newMessagePart], + } + + queryClient.setQueryData( + ["messages", sessionId], + (old: MessageWithParts[] = []) => [ + ...old, + optimisticNewMessageWithParts, + ] + ) + + const isFirstMessage = !messages || messages.length === 0 + + sendMessageMutation.mutate( + { + sessionId, + payload, + }, + { + onSuccess: () => { + if (isFirstMessage) { + queryClient.invalidateQueries({ queryKey: ["sessions", { cwd }] }) + } + }, + onError: () => { + // revert the optimistic update + setInput(enteredInput) + queryClient.setQueryData( + ["messages", sessionId], + (old: MessageWithParts[] = []) => + old.filter( + (m) => m.info.id !== optimisticNewMessageWithParts.info.id + ) + ) + }, + } + ) + } + + if (activeSession?.id) { + doSend(activeSession.id) + } else { + // Create session first + const draft = input + createSessionMutation.mutate(undefined, { + onSuccess: (newSession) => { + // Update URL to include the new session + setSearchParams((prev: URLSearchParams) => { + const next = new URLSearchParams(prev) + next.set("session", newSession.id) + return next + }) + if (cwd) setLastSession(cwd, newSession.id) + // Proceed to send draft message + setInput(draft) // restore draft into state for doSend + doSend(newSession.id) + }, + }) + } + } + + return ( +
+ setInput(e.target.value)} + value={input} + /> +
+
+ +
+ +
+ + ) +} diff --git a/src/pages/chat/components/chat-sidebar.tsx b/apps/web/src/pages/chat/components/chat-sidebar.tsx similarity index 68% rename from src/pages/chat/components/chat-sidebar.tsx rename to apps/web/src/pages/chat/components/chat-sidebar.tsx index 913a549..da09d5e 100644 --- a/src/pages/chat/components/chat-sidebar.tsx +++ b/apps/web/src/pages/chat/components/chat-sidebar.tsx @@ -1,9 +1,11 @@ import * as React from "react" +import { useLastSessionStore } from "@/store/last-session" import type { Opencode } from "@opencode-ai/sdk" import { Plus } from "lucide-react" -import { useNavigate } from "react-router-dom" +import { useSearchParams } from "react-router-dom" -import { useCreateSession, useGetSessions } from "@/hooks/fetch/sessions" +import { useGetSessions } from "@/hooks/fetch/sessions" +import { useUrlParams } from "@/hooks/use-url-params" import { Button } from "@/components/ui/button" import { @@ -19,29 +21,29 @@ import { SidebarTrigger, } from "@/components/ui/sidebar" import { Skeleton } from "@/components/ui/skeleton" +import { InstanceSwitcher } from "@/components/instance-switcher" export function ChatSidebar({ ...props }: React.ComponentProps) { const { isLoading } = useGetSessions() - const navigate = useNavigate() - const createSessionMutation = useCreateSession() + const [, setSearchParams] = useSearchParams() const handleNewChat = () => { - createSessionMutation.mutate(undefined, { - onSuccess: (newSession) => { - if (newSession && newSession.id) { - navigate(`/s/${newSession.id}`) - } - }, + // Clear session param to start a fresh draft; first message will create session + setSearchParams((prev: URLSearchParams) => { + const next = new URLSearchParams(prev) + next.delete("session") + return next }) } return ( - -
+ + +

Chats

@@ -51,10 +53,10 @@ export function ChatSidebar({ variant="secondary" size="sm" onClick={handleNewChat} - disabled={createSessionMutation.isPending || isLoading} + disabled={isLoading} > - {createSessionMutation.isPending ? "Creating..." : "New Chat"} + New Chat
{/* TODO: search for sessions */} @@ -62,7 +64,7 @@ export function ChatSidebar({ - + @@ -105,13 +107,28 @@ const SessionList = () => { } const Session = ({ session }: { session: Opencode.Session }) => { - const navigate = useNavigate() + const [, setSearchParams] = useSearchParams() + const setLastSession = useLastSessionStore((s) => s.setLastSession) + const { cwd } = useUrlParams() + + // const deleteSession = useDeleteSession() + + // const handleDelete = () => { + // deleteSession.mutate(session.id) + // } return ( navigate(`/s/${session.id}`)} + onClick={() => { + if (cwd) setLastSession(cwd, session.id) + setSearchParams((prev: URLSearchParams) => { + const next = new URLSearchParams(prev) + next.set("session", session.id) + return next + }) + }} > {session.title || session.id} ID: {session.id} diff --git a/src/pages/chat/components/chat-window.tsx b/apps/web/src/pages/chat/components/chat-window.tsx similarity index 61% rename from src/pages/chat/components/chat-window.tsx rename to apps/web/src/pages/chat/components/chat-window.tsx index eaad4a9..80630e5 100644 --- a/src/pages/chat/components/chat-window.tsx +++ b/apps/web/src/pages/chat/components/chat-window.tsx @@ -17,14 +17,22 @@ export function ChatWindow() { const ChatHeader = () => { const { data: activeSession } = useGetActiveSession() const { data: appInfo } = useGetAppInfo() + const projectLabel = appInfo?.projectName ?? appInfo?.path.root ?? "" + const hasProject = !!projectLabel + const hasSession = !!activeSession?.title + + if (!hasProject && !hasSession) return null + return (
- - {appInfo?.projectName ?? appInfo?.path.root} - - / - {activeSession?.title} + {hasProject && ( + {projectLabel} + )} + {hasProject && hasSession && ( + / + )} + {hasSession && {activeSession!.title}}
) diff --git a/apps/web/src/pages/chat/components/install-guide.tsx b/apps/web/src/pages/chat/components/install-guide.tsx new file mode 100644 index 0000000..649b9f1 --- /dev/null +++ b/apps/web/src/pages/chat/components/install-guide.tsx @@ -0,0 +1,74 @@ +import { ChevronDown, ChevronRight, Terminal } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible" +import { CopyButton } from "@/components/copy-button" + +interface InstallGuideProps { + isCollapsed: boolean + onToggle: () => void +} + +export function InstallGuide({ isCollapsed, onToggle }: InstallGuideProps) { + const installCommand = + "curl -sSL https://opencode-web.vercel.app/install.sh | bash" + + return ( + + + + +
+ {isCollapsed ? ( + + ) : ( + + )} + Install Opencode CLI +
+
+
+ + + +
+

+ Install the Opencode CLI tool to start chatting with AI about + your code. +

+ +
+ + {installCommand} + +
+
+ +
+

What happens next?

+
+
+
+ Install the CLI tool on your machine +
+
+
+ Run the command below to connect to your codebase +
+
+
+ Start chatting with AI about your code +
+
+
+ + + + + ) +} diff --git a/apps/web/src/pages/chat/components/instance-lists.tsx b/apps/web/src/pages/chat/components/instance-lists.tsx new file mode 100644 index 0000000..49dd93f --- /dev/null +++ b/apps/web/src/pages/chat/components/instance-lists.tsx @@ -0,0 +1,146 @@ +import { useMemo } from "react" +import { useLastSessionStore } from "@/store/last-session" +import { useRecentProjectsStore } from "@/store/recent-projects" +import { Clock, Copy, Folder, Play } from "lucide-react" +import { useSearchParams } from "react-router-dom" + +import { useGetInstances } from "@/hooks/fetch/broker" +import { useStartInstance } from "@/hooks/fetch/instances" + +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" + +interface MergedProject { + path: string + name: string + lastUsed: string + isOffline: boolean + source: "offline" | "recent" +} + +export function InstanceLists() { + const startInstanceMutation = useStartInstance() + const [, setSearchParams] = useSearchParams() + const removeLastSession = useLastSessionStore((s) => s.removeLastSession) + const { data: brokerData } = useGetInstances() + const { projects, getProjectName } = useRecentProjectsStore() + + const instances = brokerData?.instances || [] + const brokerStatus = brokerData?.brokerStatus || "offline" + const isBrokerOffline = brokerStatus === "offline" + + const offlineInstances = instances.filter((i) => i.status === "offline") + + // Merge offline instances and recent projects, removing duplicates + const mergedProjects = useMemo(() => { + const projectMap = new Map() + + // Add offline instances first + offlineInstances.forEach((instance) => { + projectMap.set(instance.cwd, { + path: instance.cwd, + name: getProjectName(instance.cwd), + lastUsed: new Date(instance.lastSeen || Date.now()).toISOString(), + isOffline: true, + source: "offline", + }) + }) + + // Add recent projects, but don't override offline instances + projects.forEach((project) => { + if (!projectMap.has(project.path)) { + projectMap.set(project.path, { + path: project.path, + name: project.name, + lastUsed: project.lastUsed, + isOffline: false, + source: "recent", + }) + } + }) + + // Convert to array and sort by lastUsed (newest first) + return Array.from(projectMap.values()).sort( + (a, b) => new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime() + ) + }, [offlineInstances, projects, getProjectName]) + + if (mergedProjects.length === 0) { + return null + } + + return ( + + +
+ + Codebases +
+
+ +
+ {mergedProjects.map((project) => ( +
+
+ {project.isOffline ? ( + + ) : ( + + )} +
+

{project.name}

+

+ {project.path} + {project.isOffline && " • Offline"} +

+
+
+ {isBrokerOffline ? ( +
+ + opencode-web {project.path} + + +
+ ) : ( + + )} +
+ ))} +
+
+
+ ) +} diff --git a/apps/web/src/pages/chat/components/messages.tsx b/apps/web/src/pages/chat/components/messages.tsx new file mode 100644 index 0000000..fed14c9 --- /dev/null +++ b/apps/web/src/pages/chat/components/messages.tsx @@ -0,0 +1,309 @@ +// Import OpenAPI types for Message and Part +import { useEffect, useMemo, useState } from "react" +import type { + AssistantMessageWithParts, + MessageWithParts, + ToolPartWithCompletedTool, +} from "@/types" +import type { Opencode } from "@opencode-ai/sdk" +import map from "lang-map" +import { codeToHtml } from "shiki" +import type { Diagnostic } from "vscode-languageserver-types" + +import { StickToBottom, StickToBottomContent } from "@/lib/use-stick-to-bottom" +import { cn } from "@/lib/utils" +import { useGetMessages } from "@/hooks/fetch/messages" +import { useGetActiveSession } from "@/hooks/fetch/sessions" + +import { Card } from "@/components/ui/card" + +// import { ContentDiff } from "@/components/content-diff" + +export function Messages() { + const { data: activeSession } = useGetActiveSession() + const { data: messages } = useGetMessages({ sessionId: activeSession?.id }) + + return ( + + + {messages && messages.length > 0 && ( +
+ {messages.map((message: MessageWithParts) => ( + + ))} +
+ )} +
+
+ ) +} + +function Message({ message }: { message: MessageWithParts }) { + // if just info, return null + if (!message.parts || message.parts.length === 0) { + return null + } + + return ( + <> + {message.parts.map((part: Opencode.Part) => { + return + })} + + ) +} + +function Part({ + part, + message, +}: { + part: Opencode.Part + message: MessageWithParts +}) { + switch (part.type) { + case "text": + return + case "tool": + return + default: + return null + } +} + +function TextPart({ + part, + message, +}: { + part: Opencode.Part + message: MessageWithParts +}) { + if (part.type !== "text") { + return null + } + + return ( + +
{part.text}
+
+ ) +} + +function isCompletedToolPart( + part: Opencode.Part +): part is ToolPartWithCompletedTool { + return part.type === "tool" && part.state?.status === "completed" +} + +function isAssistantMessage( + message: MessageWithParts +): message is AssistantMessageWithParts { + return message.info.role === "assistant" +} + +function ToolPart({ + part, + message, +}: { + part: Opencode.Part + message: MessageWithParts +}) { + if (part.type !== "tool") { + return null + } + + // Handle error state + if (part.state?.status === "error") { + return ( +
+ Error: {part.state.error} +
+ ) + } + + // Handle completed state + if (isCompletedToolPart(part) && isAssistantMessage(message)) { + return + } + + // Optionally handle "pending" or "running" states + return null +} + +const CompletedToolPart = ({ + part, + message, +}: { + part: ToolPartWithCompletedTool + message: AssistantMessageWithParts +}) => { + switch (part.tool) { + case "bash": + return + case "edit": + return
EDIT: lskjdl
+ // return + default: + return + } +} + +const FallbackToolComponent = ({ part }: { part: Opencode.Part }) => { + if (part.type !== "tool") return null + + return
{part.tool}
+} + +// TODO: need better way to render this +const BashToolComponent = ({ + part, +}: { + part: Opencode.Part & { type: "tool"; state: Opencode.ToolStateCompleted } +}) => { + const command = (part.state?.input?.command || "") as string + const stdout = (part.state?.metadata?.stdout || "") as string + + const [commandHtml, setCommandHtml] = useState("") + const [outputHtml, setOutputHtml] = useState("") + + // TODO: need to sync shiki theme with the theme of the app + useEffect(() => { + if (command) { + codeToHtml("> " + command, { + lang: "bash", + theme: "github-dark", + }).then(setCommandHtml) + } else { + setCommandHtml("") + } + }, [command]) + + useEffect(() => { + if (stdout) { + codeToHtml(stdout, { + lang: "bash", + theme: "github-dark", + }).then(setOutputHtml) + } else { + setOutputHtml("") + } + }, [stdout]) + + return ( +
+
+ BASH + + {(part.state?.input?.description || "") as string} +
+
+
+
+
+
+ ) +} + +function getShikiLang(filename: string) { + const ext = filename.split(".").pop()?.toLowerCase() ?? "" + const langs = map.languages(ext) + const type = langs?.[0]?.toLowerCase() + + const overrides: Record = { + conf: "shellscript", + } + + return type ? (overrides[type] ?? type) : "plaintext" +} + +function stripWorkingDirectory(filePath?: string, workingDir?: string) { + if (filePath === undefined || workingDir === undefined) return filePath + + const prefix = workingDir.endsWith("/") ? workingDir : workingDir + "/" + + if (filePath === workingDir) { + return "" + } + + if (filePath.startsWith(prefix)) { + return filePath.slice(prefix.length) + } + + return filePath +} + +function getDiagnostics( + diagnosticsByFile: Record, + currentFile: string +): React.ReactElement[] { + const result: React.ReactElement[] = [] + + if ( + diagnosticsByFile === undefined || + diagnosticsByFile[currentFile] === undefined + ) + return result + + for (const diags of Object.values(diagnosticsByFile)) { + for (const d of diags) { + if (d.severity !== 1) continue + + const line = d.range.start.line + 1 + const column = d.range.start.character + 1 + + result.push( +
+          
+            Error
+          
+          
+            [{line}:{column}]
+          
+          {d.message}
+        
+ ) + } + } + + return result +} + +const EditToolComponent = ({ + message, + part, +}: { + message: AssistantMessageWithParts + part: Opencode.Part & { type: "tool"; state: Opencode.ToolStateCompleted } +}) => { + const filePath = useMemo( + () => + stripWorkingDirectory( + (part.state.input.filePath ?? "") as string, + message.info.path.cwd + ), + [part.state.input.filePath, message.info.path.cwd] + ) + + return ( + <> +
EDIT: lskjdl
+ {/* */} + + ) +} diff --git a/apps/web/src/pages/chat/components/onboarding-screen.tsx b/apps/web/src/pages/chat/components/onboarding-screen.tsx new file mode 100644 index 0000000..c88c91b --- /dev/null +++ b/apps/web/src/pages/chat/components/onboarding-screen.tsx @@ -0,0 +1,36 @@ +import { useState } from "react" +import { useUserStateStore } from "@/store/user-state" + +import { InstallGuide } from "./install-guide" +import { InstanceLists } from "./instance-lists" +import { StartInstanceGuide } from "./start-instance-guide" + +export function OnboardingScreen() { + const { isFirstTimeUser } = useUserStateStore() + const [isInstallCollapsed, setIsInstallCollapsed] = useState(!isFirstTimeUser) + + return ( +
+
+
+

Welcome to Opencode Web

+

+ Chat with AI about your code, locally and securely. +

+
+ + {/* Install Guide - collapsed for returning users */} + setIsInstallCollapsed(!isInstallCollapsed)} + /> + + {/* Start Instance Guide - always visible */} + + + {/* Instance Lists - only if there are instances to show */} + +
+
+ ) +} diff --git a/apps/web/src/pages/chat/components/provider-select.tsx b/apps/web/src/pages/chat/components/provider-select.tsx new file mode 100644 index 0000000..2af081e --- /dev/null +++ b/apps/web/src/pages/chat/components/provider-select.tsx @@ -0,0 +1,143 @@ +import { useEffect, useMemo, useState } from "react" +import { useSelectedModelStore } from "@/store" +import { CheckIcon, ChevronsUpDownIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { useGetProviders } from "@/hooks/fetch/providers" + +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +export function ProviderSelect() { + const { data: providers } = useGetProviders() + + const setSelectedModel = useSelectedModelStore((s) => s.setSelectedModel) + const selectedModel = useSelectedModelStore((s) => s.selectedModel) + const recentModels = useSelectedModelStore((s) => s.recent) + + // TODO: Refactor + useEffect(() => { + if (selectedModel === null && providers?.default) { + setSelectedModel({ + providerID: Object.keys(providers.default)[0], + modelID: providers.default[Object.keys(providers.default)[0]], + }) + } + }, [providers]) + + const modelList = useMemo(() => { + if (!providers?.providers) return [] + return providers.providers.flatMap((provider) => + Object.keys(provider.models).map((model) => ({ + providerID: provider.id, + modelID: model, + name: provider.models[model].name, + })) + ) + }, [providers]) + + const [popoverOpen, setPopoverOpen] = useState(false) + + return ( + + + + + + + + + {/* TODO: add a guide on how to add a model */} + No model found. + {recentModels.length > 0 && ( + + {recentModels.map((model) => { + const displayName = + modelList.find((m) => m.modelID === model.modelID)?.name || + model.modelID + return ( + { + setSelectedModel(model) + setPopoverOpen(false) + }} + > + + {displayName} + + ) + })} + + )} + + {providers?.providers?.map((provider) => ( + + {Object.keys(provider.models).map((modelId) => { + const info = provider.models[modelId] + return ( + { + const parts = currentValue.split(":") + const modelId = parts.pop() ?? currentValue + const providerId = parts.pop() ?? provider.id + setSelectedModel({ + providerID: providerId, + modelID: modelId, + }) + setPopoverOpen(false) + }} + > + + {info.name} + + ) + })} + + ))} + + + + + ) +} diff --git a/src/pages/chat/components/session-chat.tsx b/apps/web/src/pages/chat/components/session-chat.tsx similarity index 88% rename from src/pages/chat/components/session-chat.tsx rename to apps/web/src/pages/chat/components/session-chat.tsx index 3ea18ef..5c8c0f0 100644 --- a/src/pages/chat/components/session-chat.tsx +++ b/apps/web/src/pages/chat/components/session-chat.tsx @@ -30,7 +30,7 @@ export function SessionChat({ className }: { className?: string }) { {(activeSessionError as Error).message}
) - if (!activeSession) return
Session not found.
+ // If no active session, we still show the input box so user can start a new chat. return (
{(messagesError as Error).message}
)} - {messages && messages.length === 0 &&
No messages found.
} + {activeSession && messages && messages.length === 0 && ( +
No messages found.
+ )}
diff --git a/apps/web/src/pages/chat/components/start-instance-guide.tsx b/apps/web/src/pages/chat/components/start-instance-guide.tsx new file mode 100644 index 0000000..cd69e81 --- /dev/null +++ b/apps/web/src/pages/chat/components/start-instance-guide.tsx @@ -0,0 +1,33 @@ +import { Play } from "lucide-react" + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { TerminalCommand } from "@/components/terminal-command" + +export function StartInstanceGuide() { + return ( + + +
+ + Connect to a Codebase +
+
+ +
+

+ Running the opencode-web CLI connects to the codebase in the current + directory. +

+ +
+ +
+

+ Or you can connect to a codebase in a specific directory. +

+ +
+
+
+ ) +} diff --git a/apps/web/src/pages/chat/index.tsx b/apps/web/src/pages/chat/index.tsx new file mode 100644 index 0000000..a484c54 --- /dev/null +++ b/apps/web/src/pages/chat/index.tsx @@ -0,0 +1,56 @@ +import { useEffect } from "react" +import { ChatSidebar } from "@/pages/chat/components/chat-sidebar" +import { ChatWindow } from "@/pages/chat/components/chat-window" +import { OnboardingScreen } from "@/pages/chat/components/onboarding-screen" +import { useUserStateStore } from "@/store/user-state" + +import { useGetInstances } from "@/hooks/fetch/broker" + +import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar" +import { Skeleton } from "@/components/ui/skeleton" + +export function ChatLayout() { + const { data: brokerData, isPending } = useGetInstances() + const { isFirstTimeUser, markAsReturningUser } = useUserStateStore() + + const instances = brokerData?.instances || [] + const onlineInstances = instances.filter((i) => i.status === "online") + + // Mark as returning user when first seeing chat UI + useEffect(() => { + if (onlineInstances.length > 0 && isFirstTimeUser) { + markAsReturningUser() + } + }, [onlineInstances.length, isFirstTimeUser, markAsReturningUser]) + + // Loading state - only show on initial load, not background refetches + if (isPending) { + return ( +
+
+ + +
+ + +
+
+
+ ) + } + + // Show chat UI if there are online instances + if (onlineInstances.length > 0) { + return ( + + + + + + + ) + } + + // Show onboarding screen if no online instances (broker offline or online but no instances) + return +} diff --git a/apps/web/src/store/index.ts b/apps/web/src/store/index.ts new file mode 100644 index 0000000..b1d7185 --- /dev/null +++ b/apps/web/src/store/index.ts @@ -0,0 +1,32 @@ +import type { SelectedModel } from "@/types" +import { create } from "zustand" +import { persist } from "zustand/middleware" + +type SelectedModelState = { + selectedModel: SelectedModel | null + recent: SelectedModel[] + setSelectedModel: (selection: SelectedModel | null) => void +} + +export const useSelectedModelStore = create()( + persist( + (set, get) => ({ + selectedModel: null, + recent: [], + setSelectedModel: (selection) => { + if (!selection) return set({ selectedModel: null }) + // update recent list: move to front, keep unique, limit 5 + const recent = get().recent.filter( + (m) => + m.modelID !== selection.modelID || + m.providerID !== selection.providerID + ) + recent.unshift(selection) + set({ selectedModel: selection, recent: recent.slice(0, 5) }) + }, + }), + { + name: "opencode:selectedModel", // localStorage key + } + ) +) diff --git a/apps/web/src/store/last-session.ts b/apps/web/src/store/last-session.ts new file mode 100644 index 0000000..336a142 --- /dev/null +++ b/apps/web/src/store/last-session.ts @@ -0,0 +1,31 @@ +import { create } from "zustand" + +interface LastSessionState { + map: Record + setLastSession: (cwd: string, sessionId: string) => void + getLastSession: (cwd: string) => string | undefined + removeLastSession: (cwd: string) => void + clearAll: () => void +} + +export const useLastSessionStore = create((set, get) => ({ + map: {}, + setLastSession: (cwd, sessionId) => + set((state) => { + const newMap = { ...state.map, [cwd]: sessionId } + console.log("[lastSession] set", cwd, sessionId, newMap) + return { map: newMap } + }), + getLastSession: (cwd) => get().map[cwd], + removeLastSession: (cwd) => + set((state) => { + const newMap = { ...state.map } + delete newMap[cwd] + console.log("[lastSession] remove", cwd, newMap) + return { map: newMap } + }), + clearAll: () => { + console.log("[lastSession] clearAll") + set({ map: {} }) + }, +})) diff --git a/apps/web/src/store/opencode-client.ts b/apps/web/src/store/opencode-client.ts new file mode 100644 index 0000000..fabc28e --- /dev/null +++ b/apps/web/src/store/opencode-client.ts @@ -0,0 +1,67 @@ +import Opencode from "@opencode-ai/sdk" +import { create } from "zustand" + +interface OpencodeClientData { + cwd: string + port: number + client: Opencode +} + +interface OpencodeClientStore { + // Map of cwd -> client data + clients: Record + + // Actions + createClient: (cwd: string, port: number) => void + removeClient: (cwd: string) => void + getClient: (cwd: string) => Opencode | null + clearAllClients: () => void +} + +export const useOpencodeClientStore = create( + (set, get) => ({ + clients: {}, + + createClient: (cwd: string, port: number) => { + const { clients } = get() + + // Don't recreate if client already exists for this cwd and port + if (clients[cwd]?.port === port && clients[cwd]?.client) { + return + } + + // Create new client + const client = new Opencode({ + baseURL: `http://localhost:${port}/api`, + maxRetries: 2, + }) + + set({ + clients: { + ...clients, + [cwd]: { cwd, port, client }, + }, + }) + }, + + removeClient: (cwd: string) => { + const { clients } = get() + const newClients = { ...clients } + + if (newClients[cwd]) { + delete newClients[cwd] + } + + set({ clients: newClients }) + }, + + getClient: (cwd: string) => { + const { clients } = get() + return clients[cwd]?.client || null + }, + + clearAllClients: () => { + set({ clients: {} }) + }, + }) +) diff --git a/apps/web/src/store/recent-projects.ts b/apps/web/src/store/recent-projects.ts new file mode 100644 index 0000000..0090fb3 --- /dev/null +++ b/apps/web/src/store/recent-projects.ts @@ -0,0 +1,55 @@ +import { create } from "zustand" +import { persist } from "zustand/middleware" + +export interface RecentProject { + path: string + lastUsed: string + name: string +} + +interface RecentProjectsState { + projects: RecentProject[] + addProject: (project: Omit) => void + removeProject: (path: string) => void + getProjectName: (path: string) => string +} + +export const useRecentProjectsStore = create()( + persist( + (set, get) => ({ + projects: [], + + addProject: (project) => { + const { projects } = get() + const existingIndex = projects.findIndex((p) => p.path === project.path) + const updatedProject = { + ...project, + lastUsed: new Date().toISOString(), + } + + if (existingIndex >= 0) { + // Update existing project + const updatedProjects = [...projects] + updatedProjects[existingIndex] = updatedProject + set({ projects: updatedProjects }) + } else { + // Add new project + set({ projects: [updatedProject, ...projects] }) + } + }, + + removeProject: (path) => { + const { projects } = get() + set({ projects: projects.filter((p) => p.path !== path) }) + }, + + getProjectName: (path) => { + const parts = path.split(/[/\\]/) + return parts[parts.length - 1] || path + }, + }), + { + name: "opencode-recent-projects", + } + ) +) diff --git a/apps/web/src/store/user-state.ts b/apps/web/src/store/user-state.ts new file mode 100644 index 0000000..59a91c9 --- /dev/null +++ b/apps/web/src/store/user-state.ts @@ -0,0 +1,19 @@ +import { create } from "zustand" +import { persist } from "zustand/middleware" + +interface UserState { + isFirstTimeUser: boolean + markAsReturningUser: () => void +} + +export const useUserStateStore = create()( + persist( + (set) => ({ + isFirstTimeUser: true, + markAsReturningUser: () => set({ isFirstTimeUser: false }), + }), + { + name: "opencode-user-state", + } + ) +) diff --git a/apps/web/src/types/index.ts b/apps/web/src/types/index.ts new file mode 100644 index 0000000..954313c --- /dev/null +++ b/apps/web/src/types/index.ts @@ -0,0 +1,34 @@ +import type { Opencode } from "@opencode-ai/sdk" + +export interface SelectedModel { + providerID: string + modelID: string +} + +// Custom structure for UI compatibility +export type MessageWithParts = { + info: Opencode.Message + parts: Opencode.Part[] +} + +export type AssistantMessageWithParts = { + info: Opencode.AssistantMessage + parts: Opencode.Part[] +} + +export type MessagePartWithCompletedTool = Opencode.Part & { + type: "tool" + state: Opencode.ToolStateCompleted + tool: string +} + +export type ToolPartWithCompletedTool = Opencode.Part & { + type: "tool" + state: Opencode.ToolStateCompleted + tool: string +} + +// export type UserMessageWithParts = { +// info: Opencode.UserMessage +// parts: Opencode.ToolStateCompleted[] +// } diff --git a/apps/web/src/types/lang-map.d.ts b/apps/web/src/types/lang-map.d.ts new file mode 100644 index 0000000..6df26d6 --- /dev/null +++ b/apps/web/src/types/lang-map.d.ts @@ -0,0 +1,27 @@ +declare module "lang-map" { + /** Returned by calling `map()` */ + export interface MapReturn { + /** All extensions keyed by language name */ + extensions: Record + /** All languages keyed by file-extension */ + languages: Record + } + + /** + * Calling `map()` gives you the raw lookup tables: + * + * ```js + * const { extensions, languages } = map(); + * ``` + */ + function map(): MapReturn + + /** Static method: get extensions for a given language */ + namespace map { + function extensions(language: string): string[] + /** Static method: get languages for a given extension */ + function languages(extension: string): string[] + } + + export = map +} diff --git a/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts similarity index 100% rename from src/vite-env.d.ts rename to apps/web/src/vite-env.d.ts diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json new file mode 100644 index 0000000..9ab1af0 --- /dev/null +++ b/apps/web/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "@workspace/typescript-config/react", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "useDefineForClassFields": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/tsconfig.json b/apps/web/tsconfig.json similarity index 100% rename from tsconfig.json rename to apps/web/tsconfig.json diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json new file mode 100644 index 0000000..f2377b5 --- /dev/null +++ b/apps/web/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "extends": "@workspace/typescript-config/node", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["vite.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..594f408 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,12 @@ +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" +import tailwindcss from "@tailwindcss/vite" +import tsconfigPaths from 'vite-tsconfig-paths' + +export default defineConfig({ + plugins: [ + react(), + tailwindcss(), + tsconfigPaths(), + ], +}) diff --git a/cli/broker.js b/cli/broker.js deleted file mode 100644 index 9cf1586..0000000 --- a/cli/broker.js +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env node -import cors from "cors" -import express from "express" - -import { BROKER_HOST } from "./lib.js" - -const port = parseInt(process.argv[2], 10) -if (!port) { - console.error("Broker: No port provided. Usage: broker.js ") - process.exit(1) -} - -const STALE_MS = 30000 -const app = express() -app.use(cors()) -app.use(express.json()) - -// Registered instances: { cwd, port, host, status, lastSeen } -let instances = [] - -function updateInstanceStatus() { - const now = Date.now() - for (const inst of instances) { - if (inst.lastSeen && now - inst.lastSeen > STALE_MS) { - inst.status = "offline" - } - } -} - -// Register instance -app.post("/register", (req, res) => { - const { cwd, port } = req.body - if (!cwd || !port) return res.status(400).send("Missing cwd or port") - let inst = instances.find((i) => i.cwd === cwd) - if (inst) { - inst.port = port - inst.lastSeen = Date.now() - inst.status = "online" - } else { - instances.push({ - cwd, - port, - host: BROKER_HOST, - status: "online", - lastSeen: Date.now(), - }) - } - res.sendStatus(200) -}) - -// Ping instance -app.post("/ping", (req, res) => { - const { cwd, port } = req.body - if (!cwd || !port) return res.status(400).send("Missing cwd or port") - let inst = instances.find((i) => i.cwd === cwd && i.port === port) - if (inst) { - inst.lastSeen = Date.now() - } - res.sendStatus(200) -}) - -// Deregister instance -app.post("/deregister", (req, res) => { - const { cwd } = req.body - if (!cwd) return res.status(400).send("Missing cwd") - let inst = instances.find((i) => i.cwd === cwd) - if (inst) { - inst.status = "offline" - } - res.json({ instances }) -}) - -// List instances -app.get("/instances", (req, res) => { - updateInstanceStatus() - res.json({ version: "1.0.0", info: { name: "opencode-web" }, instances }) -}) - -app.listen(port, BROKER_HOST, () => { - console.log(`Broker running at http://${BROKER_HOST}:${port}`) -}) diff --git a/cli/index.js b/cli/index.js deleted file mode 100644 index 3981182..0000000 --- a/cli/index.js +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env node -import { spawn } from "child_process" -import path from "path" -import { fileURLToPath } from "url" -import cors from "cors" -import express from "express" -import getPort from "get-port" -import { createProxyMiddleware } from "http-proxy-middleware" - -import { BROKER_HOST, BROKER_PORT_RANGE } from "./lib.js" - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -function delay(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)) -} - -async function isBrokerOnPort(port) { - try { - const res = await fetch(`http://${BROKER_HOST}:${port}/instances`) - if (!res.ok) return false - const data = await res.json() - return ( - Array.isArray(data.instances) && - data.info && - data.info.name === "opencode-web" - ) - } catch { - return false - } -} - -function isPortFree(port) { - return new Promise((resolve) => { - const testServer = express().listen(port, BROKER_HOST, () => { - testServer.close(() => resolve(true)) - }) - testServer.on("error", () => resolve(false)) - }) -} - -async function spawnDetachedBroker(port) { - const brokerPath = path.join(__dirname, "broker.js") - const proc = spawn(process.execPath, [brokerPath, port], { - detached: true, - stdio: "ignore", - }) - proc.unref() - // Wait for broker to be ready - for (let i = 0; i < 20; i++) { - await delay(200) - if (await isBrokerOnPort(port)) { - return port - } - } - throw new Error(`Failed to start broker on port ${port}`) -} - -async function findOrStartBroker() { - // Try to find an existing broker - for (const port of BROKER_PORT_RANGE) { - if (await isBrokerOnPort(port)) { - return port - } - } - console.log("No existing broker found, trying to start one...") - // Try to start a broker on a free port - for (const port of BROKER_PORT_RANGE) { - if (await isPortFree(port)) { - return await spawnDetachedBroker(port) - } - } - throw new Error("Could not start or find a broker in the allowed port range.") -} - -async function registerInstance(brokerPort, proxyPort, cwd) { - try { - await fetch(`http://${BROKER_HOST}:${brokerPort}/register`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ cwd, port: proxyPort }), - }) - } catch (e) { - console.error("Failed to register with broker:", e) - } -} - -async function pingInstance(brokerPort, proxyPort, cwd) { - try { - await fetch(`http://${BROKER_HOST}:${brokerPort}/ping`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ cwd, port: proxyPort }), - }) - } catch (e) { - // ignore - } -} - -async function deregisterInstance(brokerPort, cwd) { - try { - await fetch(`http://${BROKER_HOST}:${brokerPort}/deregister`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ cwd }), - }) - } catch (e) { - // ignore - } -} - -async function startProxyAndOpencode() { - const PROXY_PORT = await getPort({ port: 15096 }) - const OPENCODE_PORT = await getPort({ port: 11923 }) - const cwd = process.cwd() - - // Start opencode server - console.log(`Starting opencode server on port ${OPENCODE_PORT}...`) - const opencodeProc = spawn("opencode", ["serve", "--port", OPENCODE_PORT], { - stdio: "inherit", - shell: true, - cwd: cwd, - }) - - opencodeProc.on("error", (err) => { - console.error("Failed to start opencode server:", err) - process.exit(1) - }) - - opencodeProc.on("exit", (code, signal) => { - if (code !== 0) { - console.error( - `opencode server exited with code ${code} (signal: ${signal})` - ) - process.exit(code || 1) - } - }) - - const app = express() - app.use(cors()) - - // Proxy /api to opencode server - app.use( - "/api", - createProxyMiddleware({ - target: `http://localhost:${OPENCODE_PORT}`, - changeOrigin: true, - ws: true, - onProxyRes: (proxyRes, req, res) => { - proxyRes.headers["Access-Control-Allow-Origin"] = "*" - proxyRes.headers["Access-Control-Allow-Headers"] = "*" - proxyRes.headers["Access-Control-Allow-Methods"] = - "GET,POST,PUT,DELETE,OPTIONS" - }, - }) - ) - - app.listen(PROXY_PORT, "127.0.0.1", () => { - console.log(`Local proxy listening at http://localhost:${PROXY_PORT}`) - console.log(`Proxying /api requests to http://localhost:${OPENCODE_PORT}`) - console.log(`Open your web client: https://opencode-web.vercel.app`) - }) - - return { PROXY_PORT } -} - -// MAIN -;(async () => { - // 1. Ensure broker is running - const brokerPort = await findOrStartBroker() - console.log(`Broker running on port ${brokerPort}`) - - // 2. Start proxy and opencode - const { PROXY_PORT } = await startProxyAndOpencode() - const cwd = process.cwd() - - // 3. Register with broker - await registerInstance(brokerPort, PROXY_PORT, cwd) - // Heartbeat every 10s - const regInterval = setInterval( - () => pingInstance(brokerPort, PROXY_PORT, cwd), - 10000 - ) - - // 4. Deregister on exit - const cleanup = async () => { - clearInterval(regInterval) - await deregisterInstance(brokerPort, cwd) - process.exit(0) - } - process.on("SIGINT", cleanup) - process.on("SIGTERM", cleanup) -})() diff --git a/package.json b/package.json index 517404d..139fb78 100644 --- a/package.json +++ b/package.json @@ -1,54 +1,18 @@ { - "name": "opencode-client", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@opencode-ai/sdk": "0.1.0-alpha.20", - "@radix-ui/react-dialog": "^1.1.14", - "@radix-ui/react-dropdown-menu": "^2.1.15", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.14", - "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-tooltip": "^1.2.7", - "@tailwindcss/vite": "^4.1.11", - "@tanstack/react-query": "^5.82.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "lucide-react": "^0.525.0", - "nanoid": "^5.1.5", - "react": "^19.1.0", - "react-dom": "^19.1.0", - "react-router-dom": "^7.6.3", - "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.11", - "ulid": "^3.0.1", - "zustand": "^5.0.6" - }, + "name": "workspace", + "version": "1.0.0", + "description": "", + "main": "index.js", "devDependencies": { - "@eslint/js": "^9.30.1", "@ianvs/prettier-plugin-sort-imports": "^4.5.1", - "@types/node": "^24.0.13", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.6.0", - "eslint": "^9.30.1", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "globals": "^16.3.0", - "prettier": "3.6.2", - "tw-animate-css": "^1.3.5", - "typescript": "~5.8.3", - "typescript-eslint": "^8.35.1", - "vite": "^7.0.3" - } + "@workspace/eslint-config": "workspace:*", + "@workspace/typescript-config": "workspace:*", + "prettier": "^3.2.5" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" } diff --git a/packages/eslint-config/README.md b/packages/eslint-config/README.md new file mode 100644 index 0000000..4a414ab --- /dev/null +++ b/packages/eslint-config/README.md @@ -0,0 +1,3 @@ +# `@workspace/eslint-config` + +Shared eslint configuration for the workspace. diff --git a/packages/eslint-config/base.js b/packages/eslint-config/base.js new file mode 100644 index 0000000..86a98cd --- /dev/null +++ b/packages/eslint-config/base.js @@ -0,0 +1,16 @@ +import js from "@eslint/js" +import eslintConfigPrettier from "eslint-config-prettier" +import tseslint from "typescript-eslint" + +/** + * A shared ESLint configuration for the repository. + * + * @type {import("eslint").Linter.Config} + * */ +const config = [ + js.configs.recommended, + eslintConfigPrettier, + ...tseslint.configs.recommended, +] + +export default config diff --git a/packages/eslint-config/cli.js b/packages/eslint-config/cli.js new file mode 100644 index 0000000..2bd2670 --- /dev/null +++ b/packages/eslint-config/cli.js @@ -0,0 +1,19 @@ +import globals from "globals" + +import base from "./base.js" + +/** + * A shared ESLint configuration for React projects in the repository. + * + * @type {import("eslint").Linter.Config} + */ +export default [ + ...base, + { + files: ["**/*.{ts}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.node, + }, + }, +] diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json new file mode 100644 index 0000000..d1c578d --- /dev/null +++ b/packages/eslint-config/package.json @@ -0,0 +1,23 @@ +{ + "name": "@workspace/eslint-config", + "version": "0.0.0", + "type": "module", + "private": true, + "exports": { + "./react": "./react.js", + "./cli": "./cli.js", + "./prettier": "./prettier.js" + }, + "devDependencies": { + "@eslint/js": "^9.31.0", + "eslint": "^9.31.0", + "eslint-plugin-prettier": "^4.2.3", + "eslint-config-prettier": "^10.1.7", + "prettier": "^3.2.5", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "typescript-eslint": "^8.37.0" + } +} diff --git a/eslint.config.js b/packages/eslint-config/react.js similarity index 55% rename from eslint.config.js rename to packages/eslint-config/react.js index 1289317..6a000fe 100644 --- a/eslint.config.js +++ b/packages/eslint-config/react.js @@ -1,23 +1,25 @@ -import js from "@eslint/js" import reactHooks from "eslint-plugin-react-hooks" import reactRefresh from "eslint-plugin-react-refresh" import { globalIgnores } from "eslint/config" import globals from "globals" -import tseslint from "typescript-eslint" -export default tseslint.config([ +import base from "./base.js" + +/** + * A shared ESLint configuration for React projects in the repository. + * + * @type {import("eslint").Linter.Config} + */ +export default [ globalIgnores(["dist"]), + ...base, + reactHooks.configs["recommended-latest"], + reactRefresh.configs.vite, { files: ["**/*.{ts,tsx}"], - extends: [ - js.configs.recommended, - tseslint.configs.recommended, - reactHooks.configs["recommended-latest"], - reactRefresh.configs.vite, - ], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, }, -]) +] diff --git a/packages/typescript-config/.gitignore b/packages/typescript-config/.gitignore new file mode 100644 index 0000000..310dc27 --- /dev/null +++ b/packages/typescript-config/.gitignore @@ -0,0 +1 @@ +.tmp/ \ No newline at end of file diff --git a/tsconfig.node.json b/packages/typescript-config/base.json similarity index 68% rename from tsconfig.node.json rename to packages/typescript-config/base.json index f85a399..6e19910 100644 --- a/tsconfig.node.json +++ b/packages/typescript-config/base.json @@ -1,25 +1,19 @@ { "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2023", - "lib": ["ES2023"], "module": "ESNext", "skipLibCheck": true, - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "moduleDetection": "force", "noEmit": true, - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true - }, - "include": ["vite.config.ts"] + } } diff --git a/packages/typescript-config/node.json b/packages/typescript-config/node.json new file mode 100644 index 0000000..92873ce --- /dev/null +++ b/packages/typescript-config/node.json @@ -0,0 +1,7 @@ +{ + "extends": "./base.json", + "compilerOptions": { + "target": "ES2023", + "lib": ["ES2023"] + } +} diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json new file mode 100644 index 0000000..eeb097e --- /dev/null +++ b/packages/typescript-config/package.json @@ -0,0 +1,8 @@ +{ + "name": "@workspace/typescript-config", + "version": "0.0.0", + "private": true, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/typescript-config/react.json b/packages/typescript-config/react.json new file mode 100644 index 0000000..0ae4b7a --- /dev/null +++ b/packages/typescript-config/react.json @@ -0,0 +1,9 @@ +{ + "extends": "./base.json", + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "jsx": "react-jsx" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 502ff18..052c43e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,10 +7,59 @@ settings: importers: .: + devDependencies: + '@ianvs/prettier-plugin-sort-imports': + specifier: ^4.5.1 + version: 4.5.1(prettier@3.6.2) + '@workspace/eslint-config': + specifier: workspace:* + version: link:packages/eslint-config + '@workspace/typescript-config': + specifier: workspace:* + version: link:packages/typescript-config + prettier: + specifier: ^3.2.5 + version: 3.6.2 + + apps/cli: + dependencies: + cors: + specifier: ^2.8.5 + version: 2.8.5 + express: + specifier: ^5.1.0 + version: 5.1.0 + get-port: + specifier: ^7.1.0 + version: 7.1.0 + http-proxy-middleware: + specifier: ^3.0.5 + version: 3.0.5 + yargs: + specifier: ^18.0.0 + version: 18.0.0 + devDependencies: + '@types/cors': + specifier: ^2.8.19 + version: 2.8.19 + '@types/express': + specifier: ^5.0.3 + version: 5.0.3 + '@types/yargs': + specifier: ^17.0.24 + version: 17.0.33 + tsx: + specifier: ^4.20.3 + version: 4.20.3 + + apps/web: dependencies: - '@opencode-ai/sdk': + '@headegg-ai/sdk': specifier: 0.1.0-alpha.20 version: 0.1.0-alpha.20 + '@radix-ui/react-collapsible': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-dialog': specifier: ^1.1.14 version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -37,10 +86,10 @@ importers: version: 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tailwindcss/vite': specifier: ^4.1.11 - version: 4.1.11(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)) + version: 4.1.11(vite@7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)) '@tanstack/react-query': - specifier: ^5.82.0 - version: 5.82.0(react@19.1.0) + specifier: ^5.83.0 + version: 5.83.0(react@19.1.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -50,6 +99,9 @@ importers: cmdk: specifier: ^1.1.1 version: 1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + lang-map: + specifier: ^0.4.0 + version: 0.4.0 lucide-react: specifier: ^0.525.0 version: 0.525.0(react@19.1.0) @@ -63,30 +115,33 @@ importers: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) react-router-dom: - specifier: ^7.6.3 - version: 7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^7.7.0 + version: 7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + shiki: + specifier: ^3.8.1 + version: 3.8.1 tailwind-merge: specifier: ^3.3.1 version: 3.3.1 tailwindcss: specifier: ^4.1.11 version: 4.1.11 - ulid: - specifier: ^3.0.1 - version: 3.0.1 + tw-animate-css: + specifier: ^1.3.5 + version: 1.3.5 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.8.3)(vite@7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)) + vscode-languageserver-types: + specifier: ^3.17.5 + version: 3.17.5 zustand: specifier: ^5.0.6 version: 5.0.6(@types/react@19.1.8)(react@19.1.0) devDependencies: '@eslint/js': specifier: ^9.30.1 - version: 9.30.1 - '@ianvs/prettier-plugin-sort-imports': - specifier: ^4.5.1 - version: 4.5.1(prettier@3.6.2) - '@types/node': - specifier: ^24.0.13 - version: 24.0.13 + version: 9.31.0 '@types/react': specifier: ^19.1.8 version: 19.1.8 @@ -95,34 +150,63 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)) + version: 4.7.0(vite@7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)) + '@workspace/eslint-config': + specifier: workspace:* + version: link:../../packages/eslint-config + '@workspace/typescript-config': + specifier: workspace:* + version: link:../../packages/typescript-config eslint: specifier: ^9.30.1 - version: 9.30.1(jiti@2.4.2) + version: 9.31.0(jiti@2.4.2) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.31.0(jiti@2.4.2)) + eslint-plugin-react-refresh: + specifier: ^0.4.20 + version: 0.4.20(eslint@9.31.0(jiti@2.4.2)) + globals: + specifier: ^16.3.0 + version: 16.3.0 + vite: + specifier: ^7.0.4 + version: 7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) + + packages/eslint-config: + devDependencies: + '@eslint/js': + specifier: ^9.31.0 + version: 9.31.0 + eslint: + specifier: ^9.31.0 + version: 9.31.0(jiti@2.4.2) + eslint-config-prettier: + specifier: ^10.1.7 + version: 10.1.8(eslint@9.31.0(jiti@2.4.2)) + eslint-plugin-prettier: + specifier: ^4.2.3 + version: 4.2.5(eslint-config-prettier@10.1.8(eslint@9.31.0(jiti@2.4.2)))(eslint@9.31.0(jiti@2.4.2))(prettier@3.6.2) + eslint-plugin-react: + specifier: ^7.33.2 + version: 7.37.5(eslint@9.31.0(jiti@2.4.2)) eslint-plugin-react-hooks: specifier: ^5.2.0 - version: 5.2.0(eslint@9.30.1(jiti@2.4.2)) + version: 5.2.0(eslint@9.31.0(jiti@2.4.2)) eslint-plugin-react-refresh: specifier: ^0.4.20 - version: 0.4.20(eslint@9.30.1(jiti@2.4.2)) + version: 0.4.20(eslint@9.31.0(jiti@2.4.2)) globals: specifier: ^16.3.0 version: 16.3.0 prettier: - specifier: 3.6.2 + specifier: ^3.2.5 version: 3.6.2 - tw-animate-css: - specifier: ^1.3.5 - version: 1.3.5 - typescript: - specifier: ~5.8.3 - version: 5.8.3 typescript-eslint: - specifier: ^8.35.1 - version: 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - vite: - specifier: ^7.0.3 - version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1) + specifier: ^8.37.0 + version: 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) + + packages/typescript-config: {} packages: @@ -209,8 +293,8 @@ packages: resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.0': - resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + '@babel/types@7.28.1': + resolution: {integrity: sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==} engines: {node: '>=6.9.0'} '@esbuild/aix-ppc64@0.25.6': @@ -387,10 +471,6 @@ packages: resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.14.0': - resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.15.1': resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -399,8 +479,8 @@ packages: resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.30.1': - resolution: {integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==} + '@eslint/js@9.31.0': + resolution: {integrity: sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.6': @@ -487,7 +567,7 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@opencode-ai/sdk@0.1.0-alpha.20': + '@headegg-ai/sdk@0.1.0-alpha.20': resolution: {integrity: sha512-9nLptNbW/7MsGmrDgGd2s+0D3/MniXTPFgjLFLtMN4SKkL2vBGCvDl6QkHgL4xptiacmBV/JqNLDPmTHHtdmXg==} '@radix-ui/number@1.1.1': @@ -509,6 +589,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collapsible@1.1.11': + resolution: {integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.7': resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: @@ -859,109 +952,130 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@rolldown/pluginutils@1.0.0-beta.19': - resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==} + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - '@rollup/rollup-android-arm-eabi@4.44.2': - resolution: {integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==} + '@rollup/rollup-android-arm-eabi@4.45.1': + resolution: {integrity: sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.44.2': - resolution: {integrity: sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==} + '@rollup/rollup-android-arm64@4.45.1': + resolution: {integrity: sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.44.2': - resolution: {integrity: sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==} + '@rollup/rollup-darwin-arm64@4.45.1': + resolution: {integrity: sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.44.2': - resolution: {integrity: sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==} + '@rollup/rollup-darwin-x64@4.45.1': + resolution: {integrity: sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.44.2': - resolution: {integrity: sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==} + '@rollup/rollup-freebsd-arm64@4.45.1': + resolution: {integrity: sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.44.2': - resolution: {integrity: sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==} + '@rollup/rollup-freebsd-x64@4.45.1': + resolution: {integrity: sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.44.2': - resolution: {integrity: sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.45.1': + resolution: {integrity: sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.44.2': - resolution: {integrity: sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==} + '@rollup/rollup-linux-arm-musleabihf@4.45.1': + resolution: {integrity: sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.44.2': - resolution: {integrity: sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==} + '@rollup/rollup-linux-arm64-gnu@4.45.1': + resolution: {integrity: sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.44.2': - resolution: {integrity: sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==} + '@rollup/rollup-linux-arm64-musl@4.45.1': + resolution: {integrity: sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.44.2': - resolution: {integrity: sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==} + '@rollup/rollup-linux-loongarch64-gnu@4.45.1': + resolution: {integrity: sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': - resolution: {integrity: sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.45.1': + resolution: {integrity: sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.44.2': - resolution: {integrity: sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==} + '@rollup/rollup-linux-riscv64-gnu@4.45.1': + resolution: {integrity: sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.44.2': - resolution: {integrity: sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==} + '@rollup/rollup-linux-riscv64-musl@4.45.1': + resolution: {integrity: sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.44.2': - resolution: {integrity: sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==} + '@rollup/rollup-linux-s390x-gnu@4.45.1': + resolution: {integrity: sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.44.2': - resolution: {integrity: sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==} + '@rollup/rollup-linux-x64-gnu@4.45.1': + resolution: {integrity: sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.44.2': - resolution: {integrity: sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==} + '@rollup/rollup-linux-x64-musl@4.45.1': + resolution: {integrity: sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.44.2': - resolution: {integrity: sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==} + '@rollup/rollup-win32-arm64-msvc@4.45.1': + resolution: {integrity: sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.44.2': - resolution: {integrity: sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==} + '@rollup/rollup-win32-ia32-msvc@4.45.1': + resolution: {integrity: sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.44.2': - resolution: {integrity: sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==} + '@rollup/rollup-win32-x64-msvc@4.45.1': + resolution: {integrity: sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==} cpu: [x64] os: [win32] + '@shikijs/core@3.8.1': + resolution: {integrity: sha512-uTSXzUBQ/IgFcUa6gmGShCHr4tMdR3pxUiiWKDm8pd42UKJdYhkAYsAmHX5mTwybQ5VyGDgTjW4qKSsRvGSang==} + + '@shikijs/engine-javascript@3.8.1': + resolution: {integrity: sha512-rZRp3BM1llrHkuBPAdYAzjlF7OqlM0rm/7EWASeCcY7cRYZIrOnGIHE9qsLz5TCjGefxBFnwgIECzBs2vmOyKA==} + + '@shikijs/engine-oniguruma@3.8.1': + resolution: {integrity: sha512-KGQJZHlNY7c656qPFEQpIoqOuC4LrxjyNndRdzk5WKB/Ie87+NJCF1xo9KkOUxwxylk7rT6nhlZyTGTC4fCe1g==} + + '@shikijs/langs@3.8.1': + resolution: {integrity: sha512-TjOFg2Wp1w07oKnXjs0AUMb4kJvujML+fJ1C5cmEj45lhjbUXtziT1x2bPQb9Db6kmPhkG5NI2tgYW1/DzhUuQ==} + + '@shikijs/themes@3.8.1': + resolution: {integrity: sha512-Vu3t3BBLifc0GB0UPg2Pox1naTemrrvyZv2lkiSw3QayVV60me1ujFQwPZGgUTmwXl1yhCPW8Lieesm0CYruLQ==} + + '@shikijs/types@3.8.1': + resolution: {integrity: sha512-5C39Q8/8r1I26suLh+5TPk1DTrbY/kn3IdWA5HdizR0FhlhD05zx5nKCqhzSfDHH3p4S0ZefxWd77DLV+8FhGg==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@tailwindcss/node@4.1.11': resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} @@ -1052,11 +1166,11 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 - '@tanstack/query-core@5.82.0': - resolution: {integrity: sha512-JrjoVuaajBQtnoWSg8iaPHaT4mW73lK2t+exxHNOSMqy0+13eKLqJgTKXKImLejQIfdAHQ6Un0njEhOvUtOd5w==} + '@tanstack/query-core@5.83.0': + resolution: {integrity: sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==} - '@tanstack/react-query@5.82.0': - resolution: {integrity: sha512-mnk8/ofKEthFeMdhV1dV8YXRf+9HqvXAcciXkoo755d/ocfWq7N/Y9jGOzS3h7ZW9dDGwSIhs3/HANWUBsyqYg==} + '@tanstack/react-query@5.83.0': + resolution: {integrity: sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==} peerDependencies: react: ^18 || ^19 @@ -1072,14 +1186,50 @@ packages: '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@5.0.7': + resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==} + + '@types/express@5.0.3': + resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/http-proxy@1.17.16': + resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@24.0.13': - resolution: {integrity: sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@24.0.14': + resolution: {integrity: sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} '@types/react-dom@19.1.6': resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} @@ -1089,70 +1239,92 @@ packages: '@types/react@19.1.8': resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} - '@typescript-eslint/eslint-plugin@8.36.0': - resolution: {integrity: sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==} + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/eslint-plugin@8.37.0': + resolution: {integrity: sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.36.0 + '@typescript-eslint/parser': ^8.37.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.36.0': - resolution: {integrity: sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==} + '@typescript-eslint/parser@8.37.0': + resolution: {integrity: sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/project-service@8.36.0': - resolution: {integrity: sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==} + '@typescript-eslint/project-service@8.37.0': + resolution: {integrity: sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.36.0': - resolution: {integrity: sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==} + '@typescript-eslint/scope-manager@8.37.0': + resolution: {integrity: sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.36.0': - resolution: {integrity: sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==} + '@typescript-eslint/tsconfig-utils@8.37.0': + resolution: {integrity: sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/type-utils@8.36.0': - resolution: {integrity: sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==} + '@typescript-eslint/type-utils@8.37.0': + resolution: {integrity: sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.36.0': - resolution: {integrity: sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==} + '@typescript-eslint/types@8.37.0': + resolution: {integrity: sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.36.0': - resolution: {integrity: sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==} + '@typescript-eslint/typescript-estree@8.37.0': + resolution: {integrity: sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.36.0': - resolution: {integrity: sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==} + '@typescript-eslint/utils@8.37.0': + resolution: {integrity: sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/visitor-keys@8.36.0': - resolution: {integrity: sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==} + '@typescript-eslint/visitor-keys@8.37.0': + resolution: {integrity: sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitejs/plugin-react@4.6.0': - resolution: {integrity: sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -1167,10 +1339,18 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1178,9 +1358,49 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1196,6 +1416,22 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1203,10 +1439,19 @@ packages: caniuse-lite@1.0.30001727: resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -1214,6 +1459,10 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1231,16 +1480,39 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1248,6 +1520,18 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -1260,6 +1544,22 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} @@ -1267,13 +1567,66 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - electron-to-chromium@1.5.182: - resolution: {integrity: sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.187: + resolution: {integrity: sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==} + + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} enhanced-resolve@5.18.2: resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} engines: {node: '>=10.13.0'} + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + esbuild@0.25.6: resolution: {integrity: sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==} engines: {node: '>=18'} @@ -1283,10 +1636,30 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@4.2.5: + resolution: {integrity: sha512-9Ni+xgemM2IWLq6aXEpP2+V/V30GeA/46Ar629vcMqVPodFFWC9skHu/D1phvuqtS8bJCFnNf01/qcmqYEwNfg==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + eslint-plugin-react-hooks@5.2.0: resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} engines: {node: '>=10'} @@ -1298,6 +1671,12 @@ packages: peerDependencies: eslint: '>=8.40' + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1310,8 +1689,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.30.1: - resolution: {integrity: sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==} + eslint@9.31.0: + resolution: {integrity: sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1340,9 +1719,23 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1372,6 +1765,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1383,19 +1780,77 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1412,16 +1867,75 @@ packages: resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} engines: {node: '>=18'} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-middleware@3.0.5: + resolution: {integrity: sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1438,21 +1952,134 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + jiti@2.4.2: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true @@ -1483,9 +2110,20 @@ packages: engines: {node: '>=6'} hasBin: true + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + lang-map@0.4.0: + resolution: {integrity: sha512-oiSqZIEUnWdFeDNsp4HId4tAxdFbx5iMBOwA3666Fn2L8Khj8NiD9xRvMsGmKXopPVkaDFtSv3CJOmXFUB0Hcg==} + engines: {node: '>=0.10.0'} + + language-map@1.5.0: + resolution: {integrity: sha512-n7gFZpe+DwEAX9cXVTw43i3wiudWDDtSn28RmdnS/HCPr284dQI/SztsamWanRr75oSlKSaGbV2nmWCTzGCoVg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1561,6 +2199,10 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1572,14 +2214,52 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1616,13 +2296,62 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.3: + resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1635,6 +2364,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1643,6 +2376,13 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1650,10 +2390,14 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -1662,23 +2406,52 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + prettier@3.6.2: resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@19.1.0: + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: react: ^19.1.0 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -1703,15 +2476,15 @@ packages: '@types/react': optional: true - react-router-dom@7.6.3: - resolution: {integrity: sha512-DiWJm9qdUAmiJrVWaeJdu4TKu13+iB/8IEi0EW/XgaHCjW/vWGrwzup0GVvaMteuZjKnh5bEvJP/K0MDnzawHw==} + react-router-dom@7.7.0: + resolution: {integrity: sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router@7.6.3: - resolution: {integrity: sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==} + react-router@7.7.0: + resolution: {integrity: sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -1734,22 +2507,71 @@ packages: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.44.2: - resolution: {integrity: sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==} + rollup@4.45.1: + resolution: {integrity: sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -1762,9 +2584,32 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1773,10 +2618,74 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@3.8.1: + resolution: {integrity: sha512-+MYIyjwGPCaegbpBeFN9+oOifI8CKiKG3awI/6h3JeT85c//H2wDW/xCJEGuQ5jPqtbboKNqNy+JyX9PYpGwNg==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1785,6 +2694,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} @@ -1807,15 +2720,37 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + tw-animate-css@1.3.5: resolution: {integrity: sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA==} @@ -1823,8 +2758,28 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.36.0: - resolution: {integrity: sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.37.0: + resolution: {integrity: sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -1835,13 +2790,32 @@ packages: engines: {node: '>=14.17'} hasBin: true - ulid@3.0.1: - resolution: {integrity: sha512-dPJyqPzx8preQhqq24bBG1YNkvigm87K8kVEHCD+ruZg24t6IFEFv00xMWfxcC4djmFtiTLdFuADn4+DOz6R7Q==} - hasBin: true + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} undici-types@7.8.0: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -1871,8 +2845,26 @@ packages: '@types/react': optional: true - vite@7.0.4: - resolution: {integrity: sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@7.0.5: + resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1911,6 +2903,25 @@ packages: yaml: optional: true + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1920,6 +2931,17 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -1927,6 +2949,14 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1949,6 +2979,9 @@ packages: use-sync-external-store: optional: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@ampproject/remapping@2.3.0': @@ -1975,7 +3008,7 @@ snapshots: '@babel/parser': 7.28.0 '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 convert-source-map: 2.0.0 debug: 4.4.1 gensync: 1.0.0-beta.2 @@ -1987,7 +3020,7 @@ snapshots: '@babel/generator@7.28.0': dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 @@ -2005,7 +3038,7 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 transitivePeerDependencies: - supports-color @@ -2029,11 +3062,11 @@ snapshots: '@babel/helpers@7.27.6': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@babel/parser@7.28.0': dependencies: - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': dependencies: @@ -2049,7 +3082,7 @@ snapshots: dependencies: '@babel/code-frame': 7.27.1 '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@babel/traverse@7.28.0': dependencies: @@ -2058,12 +3091,12 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 debug: 4.4.1 transitivePeerDependencies: - supports-color - '@babel/types@7.28.0': + '@babel/types@7.28.1': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -2146,9 +3179,9 @@ snapshots: '@esbuild/win32-x64@0.25.6': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.31.0(jiti@2.4.2))': dependencies: - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.31.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -2163,10 +3196,6 @@ snapshots: '@eslint/config-helpers@0.3.0': {} - '@eslint/core@0.14.0': - dependencies: - '@types/json-schema': 7.0.15 - '@eslint/core@0.15.1': dependencies: '@types/json-schema': 7.0.15 @@ -2185,7 +3214,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.30.1': {} + '@eslint/js@9.31.0': {} '@eslint/object-schema@2.1.6': {} @@ -2229,7 +3258,7 @@ snapshots: '@babel/generator': 7.28.0 '@babel/parser': 7.28.0 '@babel/traverse': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 prettier: 3.6.2 semver: 7.7.2 transitivePeerDependencies: @@ -2280,6 +3309,22 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) @@ -2636,68 +3681,101 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@rolldown/pluginutils@1.0.0-beta.19': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} - '@rollup/rollup-android-arm-eabi@4.44.2': + '@rollup/rollup-android-arm-eabi@4.45.1': optional: true - '@rollup/rollup-android-arm64@4.44.2': + '@rollup/rollup-android-arm64@4.45.1': optional: true - '@rollup/rollup-darwin-arm64@4.44.2': + '@rollup/rollup-darwin-arm64@4.45.1': optional: true - '@rollup/rollup-darwin-x64@4.44.2': + '@rollup/rollup-darwin-x64@4.45.1': optional: true - '@rollup/rollup-freebsd-arm64@4.44.2': + '@rollup/rollup-freebsd-arm64@4.45.1': optional: true - '@rollup/rollup-freebsd-x64@4.44.2': + '@rollup/rollup-freebsd-x64@4.45.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.44.2': + '@rollup/rollup-linux-arm-gnueabihf@4.45.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.44.2': + '@rollup/rollup-linux-arm-musleabihf@4.45.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.44.2': + '@rollup/rollup-linux-arm64-gnu@4.45.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.44.2': + '@rollup/rollup-linux-arm64-musl@4.45.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.44.2': + '@rollup/rollup-linux-loongarch64-gnu@4.45.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': + '@rollup/rollup-linux-powerpc64le-gnu@4.45.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.44.2': + '@rollup/rollup-linux-riscv64-gnu@4.45.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.44.2': + '@rollup/rollup-linux-riscv64-musl@4.45.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.44.2': + '@rollup/rollup-linux-s390x-gnu@4.45.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.44.2': + '@rollup/rollup-linux-x64-gnu@4.45.1': optional: true - '@rollup/rollup-linux-x64-musl@4.44.2': + '@rollup/rollup-linux-x64-musl@4.45.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.44.2': + '@rollup/rollup-win32-arm64-msvc@4.45.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.44.2': + '@rollup/rollup-win32-ia32-msvc@4.45.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.44.2': + '@rollup/rollup-win32-x64-msvc@4.45.1': optional: true + '@shikijs/core@3.8.1': + dependencies: + '@shikijs/types': 3.8.1 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.8.1': + dependencies: + '@shikijs/types': 3.8.1 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.3 + + '@shikijs/engine-oniguruma@3.8.1': + dependencies: + '@shikijs/types': 3.8.1 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.8.1': + dependencies: + '@shikijs/types': 3.8.1 + + '@shikijs/themes@3.8.1': + dependencies: + '@shikijs/types': 3.8.1 + + '@shikijs/types@3.8.1': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@tailwindcss/node@4.1.11': dependencies: '@ampproject/remapping': 2.3.0 @@ -2762,49 +3840,95 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 - '@tailwindcss/vite@4.1.11(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1))': + '@tailwindcss/vite@4.1.11(vite@7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3))': dependencies: '@tailwindcss/node': 4.1.11 '@tailwindcss/oxide': 4.1.11 tailwindcss: 4.1.11 - vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) - '@tanstack/query-core@5.82.0': {} + '@tanstack/query-core@5.83.0': {} - '@tanstack/react-query@5.82.0(react@19.1.0)': + '@tanstack/react-query@5.83.0(react@19.1.0)': dependencies: - '@tanstack/query-core': 5.82.0 + '@tanstack/query-core': 5.83.0 react: 19.1.0 '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.7 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@types/babel__traverse@7.20.7': dependencies: - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.0.14 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.0.14 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 24.0.14 '@types/estree@1.0.8': {} + '@types/express-serve-static-core@5.0.7': + dependencies: + '@types/node': 24.0.14 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@5.0.3': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.0.7 + '@types/serve-static': 1.15.8 + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/http-errors@2.0.5': {} + + '@types/http-proxy@1.17.16': + dependencies: + '@types/node': 24.0.14 + '@types/json-schema@7.0.15': {} - '@types/node@24.0.13': + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mime@1.3.5': {} + + '@types/node@24.0.14': dependencies: undici-types: 7.8.0 + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: '@types/react': 19.1.8 @@ -2813,15 +3937,34 @@ snapshots: dependencies: csstype: 3.1.3 - '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.0.14 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.0.14 + '@types/send': 0.17.5 + + '@types/unist@3.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.36.0 - '@typescript-eslint/type-utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.36.0 - eslint: 9.30.1(jiti@2.4.2) + '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.37.0 + '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.37.0 + eslint: 9.31.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -2830,55 +3973,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.36.0 - '@typescript-eslint/types': 8.36.0 - '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.36.0 + '@typescript-eslint/scope-manager': 8.37.0 + '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.37.0 debug: 4.4.1 - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.31.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.36.0(typescript@5.8.3)': + '@typescript-eslint/project-service@8.37.0(typescript@5.8.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) - '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/tsconfig-utils': 8.37.0(typescript@5.8.3) + '@typescript-eslint/types': 8.37.0 debug: 4.4.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.36.0': + '@typescript-eslint/scope-manager@8.37.0': dependencies: - '@typescript-eslint/types': 8.36.0 - '@typescript-eslint/visitor-keys': 8.36.0 + '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/visitor-keys': 8.37.0 - '@typescript-eslint/tsconfig-utils@8.36.0(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.37.0(typescript@5.8.3)': dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.1 - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.31.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.36.0': {} + '@typescript-eslint/types@8.37.0': {} - '@typescript-eslint/typescript-estree@8.36.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.37.0(typescript@5.8.3)': dependencies: - '@typescript-eslint/project-service': 8.36.0(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) - '@typescript-eslint/types': 8.36.0 - '@typescript-eslint/visitor-keys': 8.36.0 + '@typescript-eslint/project-service': 8.37.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.37.0(typescript@5.8.3) + '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/visitor-keys': 8.37.0 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -2889,34 +4033,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/utils@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.36.0 - '@typescript-eslint/types': 8.36.0 - '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) - eslint: 9.30.1(jiti@2.4.2) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.37.0 + '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) + eslint: 9.31.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.36.0': + '@typescript-eslint/visitor-keys@8.37.0': dependencies: - '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/types': 8.37.0 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-react@4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1))': + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-react@4.7.0(vite@7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) - '@rolldown/pluginutils': 1.0.0-beta.19 + '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) transitivePeerDependencies: - supports-color + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -2930,18 +4081,99 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@6.1.0: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.1: {} + argparse@2.0.1: {} aria-hidden@1.2.6: dependencies: tslib: 2.8.1 + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + balanced-match@1.0.2: {} + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.1 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2958,25 +4190,56 @@ snapshots: browserslist@4.25.1: dependencies: caniuse-lite: 1.0.30001727 - electron-to-chromium: 1.5.182 + electron-to-chromium: 1.5.187 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.1) + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} caniuse-lite@1.0.30001727: {} + ccount@2.0.1: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + chownr@3.0.0: {} class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + clsx@2.1.1: {} cmdk@1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -2997,12 +4260,29 @@ snapshots: color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + concat-map@0.0.1: {} + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + cookie@1.0.2: {} + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3011,23 +4291,178 @@ snapshots: csstype@3.1.3: {} + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + debug@4.4.1: dependencies: ms: 2.1.3 deep-is@0.1.4: {} + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + depd@2.0.0: {} + + dequal@2.0.3: {} + detect-libc@2.0.4: {} detect-node-es@1.1.0: {} - electron-to-chromium@1.5.182: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.187: {} + + emoji-regex@10.4.0: {} + + encodeurl@2.0.0: {} enhanced-resolve@5.18.2: dependencies: graceful-fs: 4.2.11 tapable: 2.2.2 + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + esbuild@0.25.6: optionalDependencies: '@esbuild/aix-ppc64': 0.25.6 @@ -3059,15 +4494,51 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.2.0(eslint@9.30.1(jiti@2.4.2)): + eslint-config-prettier@10.1.8(eslint@9.31.0(jiti@2.4.2)): + dependencies: + eslint: 9.31.0(jiti@2.4.2) + + eslint-plugin-prettier@4.2.5(eslint-config-prettier@10.1.8(eslint@9.31.0(jiti@2.4.2)))(eslint@9.31.0(jiti@2.4.2))(prettier@3.6.2): + dependencies: + eslint: 9.31.0(jiti@2.4.2) + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.31.0(jiti@2.4.2)) + + eslint-plugin-react-hooks@5.2.0(eslint@9.31.0(jiti@2.4.2)): + dependencies: + eslint: 9.31.0(jiti@2.4.2) + + eslint-plugin-react-refresh@0.4.20(eslint@9.31.0(jiti@2.4.2)): dependencies: - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.31.0(jiti@2.4.2) - eslint-plugin-react-refresh@0.4.20(eslint@9.30.1(jiti@2.4.2)): + eslint-plugin-react@7.37.5(eslint@9.31.0(jiti@2.4.2)): dependencies: - eslint: 9.30.1(jiti@2.4.2) + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.31.0(jiti@2.4.2) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 eslint-scope@8.4.0: dependencies: @@ -3078,15 +4549,15 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.30.1(jiti@2.4.2): + eslint@9.31.0(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.0 - '@eslint/core': 0.14.0 + '@eslint/core': 0.15.1 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.30.1 + '@eslint/js': 9.31.0 '@eslint/plugin-kit': 0.3.3 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 @@ -3138,8 +4609,46 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + + eventemitter3@4.0.7: {} + + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3156,9 +4665,9 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.4.6(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.3): optionalDependencies: - picomatch: 4.0.2 + picomatch: 4.0.3 file-entry-cache@8.0.0: dependencies: @@ -3168,6 +4677,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3180,13 +4700,72 @@ snapshots: flatted@3.3.3: {} + follow-redirects@1.15.9(debug@4.4.1): + optionalDependencies: + debug: 4.4.1 + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fsevents@2.3.3: optional: true - gensync@1.0.0-beta.2: {} + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.3.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 get-nonce@1.0.1: {} + get-port@7.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3199,12 +4778,92 @@ snapshots: globals@16.3.0: {} + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globrex@0.1.2: {} + + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} + has-bigints@1.1.0: {} + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + html-void-elements@3.0.0: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-middleware@3.0.5: + dependencies: + '@types/http-proxy': 1.17.16 + debug: 4.4.1 + http-proxy: 1.18.1(debug@4.4.1) + is-glob: 4.0.3 + is-plain-object: 5.0.0 + micromatch: 4.0.8 + transitivePeerDependencies: + - supports-color + + http-proxy@1.18.1(debug@4.4.1): + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.9(debug@4.4.1) + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -3216,16 +4875,140 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + ipaddr.js@1.9.1: {} + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-number@7.0.0: {} + is-plain-object@5.0.0: {} + + is-promise@4.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + isexe@2.0.0: {} + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + jiti@2.4.2: {} js-tokens@4.0.0: {} @@ -3244,10 +5027,23 @@ snapshots: json5@2.2.3: {} + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + lang-map@0.4.0: + dependencies: + language-map: 1.5.0 + + language-map@1.5.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -3304,6 +5100,10 @@ snapshots: lodash.merge@4.6.2: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -3316,13 +5116,54 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 + math-intrinsics@1.1.0: {} + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.54.0: {} + + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -3347,8 +5188,62 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + node-releases@2.0.19: {} + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.3: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.0.1 + regex-recursion: 6.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3358,6 +5253,12 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -3370,15 +5271,23 @@ snapshots: dependencies: callsites: 3.1.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-key@3.1.1: {} + path-parse@1.0.7: {} + + path-to-regexp@8.2.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} - picomatch@4.0.2: {} + picomatch@4.0.3: {} + + possible-typed-array-names@1.1.0: {} postcss@8.5.6: dependencies: @@ -3388,17 +5297,49 @@ snapshots: prelude-ls@1.2.1: {} + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + prettier@3.6.2: {} + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@7.1.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + punycode@2.3.1: {} + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} + range-parser@1.2.1: {} + + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 scheduler: 0.26.0 + react-is@16.13.1: {} + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): @@ -3420,13 +5361,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 - react-router-dom@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-router-dom@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - react-router: 7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: 7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react-router@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-router@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: cookie: 1.0.2 react: 19.1.0 @@ -3444,62 +5385,295 @@ snapshots: react@19.1.0: {} + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + requires-port@1.0.0: {} + resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + reusify@1.1.0: {} - rollup@4.44.2: + rollup@4.45.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.44.2 - '@rollup/rollup-android-arm64': 4.44.2 - '@rollup/rollup-darwin-arm64': 4.44.2 - '@rollup/rollup-darwin-x64': 4.44.2 - '@rollup/rollup-freebsd-arm64': 4.44.2 - '@rollup/rollup-freebsd-x64': 4.44.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.44.2 - '@rollup/rollup-linux-arm-musleabihf': 4.44.2 - '@rollup/rollup-linux-arm64-gnu': 4.44.2 - '@rollup/rollup-linux-arm64-musl': 4.44.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.44.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.44.2 - '@rollup/rollup-linux-riscv64-gnu': 4.44.2 - '@rollup/rollup-linux-riscv64-musl': 4.44.2 - '@rollup/rollup-linux-s390x-gnu': 4.44.2 - '@rollup/rollup-linux-x64-gnu': 4.44.2 - '@rollup/rollup-linux-x64-musl': 4.44.2 - '@rollup/rollup-win32-arm64-msvc': 4.44.2 - '@rollup/rollup-win32-ia32-msvc': 4.44.2 - '@rollup/rollup-win32-x64-msvc': 4.44.2 + '@rollup/rollup-android-arm-eabi': 4.45.1 + '@rollup/rollup-android-arm64': 4.45.1 + '@rollup/rollup-darwin-arm64': 4.45.1 + '@rollup/rollup-darwin-x64': 4.45.1 + '@rollup/rollup-freebsd-arm64': 4.45.1 + '@rollup/rollup-freebsd-x64': 4.45.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.45.1 + '@rollup/rollup-linux-arm-musleabihf': 4.45.1 + '@rollup/rollup-linux-arm64-gnu': 4.45.1 + '@rollup/rollup-linux-arm64-musl': 4.45.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.45.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.45.1 + '@rollup/rollup-linux-riscv64-gnu': 4.45.1 + '@rollup/rollup-linux-riscv64-musl': 4.45.1 + '@rollup/rollup-linux-s390x-gnu': 4.45.1 + '@rollup/rollup-linux-x64-gnu': 4.45.1 + '@rollup/rollup-linux-x64-musl': 4.45.1 + '@rollup/rollup-win32-arm64-msvc': 4.45.1 + '@rollup/rollup-win32-ia32-msvc': 4.45.1 + '@rollup/rollup-win32-x64-msvc': 4.45.1 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.1 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + scheduler@0.26.0: {} semver@6.3.1: {} semver@7.7.2: {} + send@1.2.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + shiki@3.8.1: + dependencies: + '@shikijs/core': 3.8.1 + '@shikijs/engine-javascript': 3.8.1 + '@shikijs/engine-oniguruma': 3.8.1 + '@shikijs/langs': 3.8.1 + '@shikijs/themes': 3.8.1 + '@shikijs/types': 3.8.1 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + source-map-js@1.2.1: {} + space-separated-tokens@2.0.2: {} + + statuses@2.0.1: {} + + statuses@2.0.2: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + strip-json-comments@3.1.1: {} supports-color@7.2.0: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@3.3.1: {} tailwindcss@4.1.11: {} @@ -3517,41 +5691,126 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + + trim-lines@3.0.1: {} + ts-api-utils@2.1.0(typescript@5.8.3): dependencies: typescript: 5.8.3 + tsconfck@3.1.6(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + tslib@2.8.1: {} + tsx@4.20.3: + dependencies: + esbuild: 0.25.6 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + tw-animate-css@1.3.5: {} type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + type-is@2.0.1: dependencies: - '@typescript-eslint/eslint-plugin': 8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/parser': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.30.1(jiti@2.4.2) + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.31.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color typescript@5.8.3: {} - ulid@3.0.1: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 undici-types@7.8.0: {} + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + unpipe@1.0.0: {} + update-browserslist-db@1.1.3(browserslist@4.25.1): dependencies: browserslist: 4.25.1 @@ -3577,19 +5836,86 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 - vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1): + vary@1.1.2: {} + + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)): + dependencies: + debug: 4.4.1 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@5.8.3) + optionalDependencies: + vite: 7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) + transitivePeerDependencies: + - supports-color + - typescript + + vite@7.0.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3): dependencies: esbuild: 0.25.6 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.44.2 + rollup: 4.45.1 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 24.0.13 + '@types/node': 24.0.14 fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.30.1 + tsx: 4.20.3 + + vscode-languageserver-types@3.17.5: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 which@2.0.2: dependencies: @@ -3597,13 +5923,36 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@5.0.0: {} + yargs-parser@22.0.0: {} + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yocto-queue@0.1.0: {} zustand@5.0.6(@types/react@19.1.8)(react@19.1.0): optionalDependencies: '@types/react': 19.1.8 react: 19.1.0 + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..0e5a073 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "packages/*" + - "apps/*" diff --git a/prettier.config.cjs b/prettier.config.cjs index f508aa8..131baf5 100644 --- a/prettier.config.cjs +++ b/prettier.config.cjs @@ -7,7 +7,6 @@ module.exports = { trailingComma: "es5", importOrder: [ "^(react/(.*)$)|^(react$)", - "^(next/(.*)$)|^(next$)", "", "", "^types$", diff --git a/src/pages/chat/components/chat-input.tsx b/src/pages/chat/components/chat-input.tsx deleted file mode 100644 index 727f309..0000000 --- a/src/pages/chat/components/chat-input.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { useState } from "react" -import { ProviderSelect } from "@/pages/chat/components/provider-select" -import { useSelectedModelStore } from "@/store" -import type { MessageWithParts } from "@/types" -import type { Opencode } from "@opencode-ai/sdk" -import { useQueryClient } from "@tanstack/react-query" - -import { generateNewID, ID } from "@/lib/generateId" -import { useGetMessages, useSendMessage } from "@/hooks/fetch/messages" -import { useGetActiveSession } from "@/hooks/fetch/sessions" - -import { ChatInputSubmit, ChatInputTextarea } from "@/components/chat-input" - -export function ChatInput() { - const { data: activeSession } = useGetActiveSession() - - const { data: messages } = useGetMessages({ sessionId: activeSession?.id }) - - const [input, setInput] = useState("") - - const sendMessageMutation = useSendMessage() - const selectedModel = useSelectedModelStore((s) => s.selectedModel) - - const queryClient = useQueryClient() - - const onSubmit = (e: React.FormEvent) => { - e.preventDefault() - if (!input.trim() || !activeSession?.id || !selectedModel) return - - const messageID = generateNewID(ID.MESSAGE) - // add a temp user message to the messages array - const newMessagePart: Opencode.TextPart = { - id: generateNewID(ID.PART), - sessionID: activeSession.id, - messageID, - type: "text", - text: input, - } - - const payload: Opencode.SessionChatParams = { - messageID, - providerID: selectedModel.providerID, - modelID: selectedModel.modelID, - mode: "build", - parts: [newMessagePart], - } - - const enteredInput = input - setInput("") - - const optimisticNewMessageWithParts: MessageWithParts = { - info: { - role: "user", - sessionID: activeSession.id, - time: { - created: Date.now(), - }, - id: messageID, - }, - parts: [newMessagePart], - } - - queryClient.setQueryData( - ["messages", activeSession?.id], - (old: MessageWithParts[] = []) => [...old, optimisticNewMessageWithParts] - ) - - const isFirstMessage = !messages || messages.length === 0 - - sendMessageMutation.mutate( - { - sessionId: activeSession?.id, - payload, - }, - { - onSuccess: () => { - if (isFirstMessage) { - queryClient.invalidateQueries({ queryKey: ["sessions"] }) - } - }, - onError: () => { - // revert the optimistic update - setInput(enteredInput) - queryClient.setQueryData( - ["messages", activeSession?.id], - (old: MessageWithParts[] = []) => - old.filter( - (m) => m.info.id !== optimisticNewMessageWithParts.info.id - ) - ) - }, - } - ) - } - - return ( -
- setInput(e.target.value)} - value={input} - /> -
-
- -
- -
- - ) -} diff --git a/src/pages/chat/components/messages.tsx b/src/pages/chat/components/messages.tsx deleted file mode 100644 index 1c54407..0000000 --- a/src/pages/chat/components/messages.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import type { MessageWithParts } from "@/types" -import type { Opencode } from "@opencode-ai/sdk" - -import { StickToBottom, StickToBottomContent } from "@/lib/use-stick-to-bottom" -import { useGetMessages } from "@/hooks/fetch/messages" -import { useGetActiveSession } from "@/hooks/fetch/sessions" - -import { Card } from "@/components/ui/card" - -export function Messages() { - const { data: activeSession } = useGetActiveSession() - const { data: messages } = useGetMessages({ sessionId: activeSession?.id }) - - return ( - - - {messages && messages.length > 0 && ( -
- {messages.map((msg: MessageWithParts) => ( - -
- {/*
{JSON.stringify(msg.info, null, 2)}
*/} - {msg.info.role === "user" ? "User" : "Assistant"} -
- {msg.parts && - msg.parts.map((part: Opencode.Part, i: number) => { - // if ( - // part.type === "step-start" || - // part.type === "step-finish" - // ) - // return null - if (part.type === "text") { - return ( -
- {part.text} -
- ) - } - if (part.type === "tool") { - return ( -
-
- [tool: {part.tool}] -
- {part.state && ( -
- - status: {part.state.status} - - {part.tool && ( - - {part.tool} - - )} -
- )} - {/* {part. && ( -
- - input: - -
-                                {JSON.stringify(part.state.input, null, 2)}
-                              
-
- )} - {part.state?.output && ( -
- - output: - -
-                                {typeof part.state.output === "string"
-                                  ? part.state.output
-                                  : JSON.stringify(
-                                      part.state.output,
-                                      null,
-                                      2
-                                    )}
-                              
-
- )} - {part.state?.metadata && ( -
- - metadata: - -
-                                {JSON.stringify(part.state.metadata, null, 2)}
-                              
-
- )} */} -
- ) - } - return ( -
- [{part.type}] -
- ) - - return null - })} -
- ))} -
- )} -
-
- ) -} diff --git a/src/pages/chat/components/provider-select.tsx b/src/pages/chat/components/provider-select.tsx deleted file mode 100644 index 4831724..0000000 --- a/src/pages/chat/components/provider-select.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useEffect, useMemo, useState } from "react" -import { useSelectedModelStore } from "@/store" -import { CheckIcon, ChevronsUpDownIcon } from "lucide-react" - -import { cn } from "@/lib/utils" -import { useGetProviders } from "@/hooks/fetch/providers" - -import { Button } from "@/components/ui/button" -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" - -export function ProviderSelect() { - const { data: providers } = useGetProviders() - - const setSelectedModel = useSelectedModelStore((s) => s.setSelectedModel) - const selectedModel = useSelectedModelStore((s) => s.selectedModel) - - // TODO: Refactor - useEffect(() => { - if (selectedModel === null && providers?.default) { - setSelectedModel({ - providerID: Object.keys(providers.default)[0], - modelID: providers.default[Object.keys(providers.default)[0]], - }) - } - }, [providers]) - - const modelList = useMemo(() => { - if (!providers?.providers) return [] - return providers.providers.flatMap((provider) => - Object.keys(provider.models).map((model) => ({ - providerID: provider.id, - modelID: model, - name: provider.models[model].name, - })) - ) - }, [providers]) - - const [popoverOpen, setPopoverOpen] = useState(false) - - return ( - - - - - - - - - No framework found. - - {modelList.map((model) => ( - { - setSelectedModel({ - providerID: - modelList.find( - (model) => model.modelID === currentValue - )?.providerID ?? "", - modelID: currentValue, - }) - setPopoverOpen(false) - }} - > - - {model.name} - - ))} - - - - - - ) -} diff --git a/src/pages/chat/index.tsx b/src/pages/chat/index.tsx deleted file mode 100644 index 2aac9f4..0000000 --- a/src/pages/chat/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ChatSidebar } from "@/pages/chat/components/chat-sidebar" -import { ChatWindow } from "@/pages/chat/components/chat-window" - -import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar" - -export function ChatLayout() { - return ( - - - - - - - ) -} diff --git a/src/store/index.ts b/src/store/index.ts deleted file mode 100644 index c6c99f3..0000000 --- a/src/store/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { SelectedModel } from "@/types" -import { create } from "zustand" - -type SelectedModelState = { - selectedModel: SelectedModel | null - setSelectedModel: (selection: SelectedModel | null) => void -} - -export const useSelectedModelStore = create((set) => ({ - selectedModel: null, - setSelectedModel: (selection) => set({ selectedModel: selection }), -})) diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index 8c88ec6..0000000 --- a/src/types/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Opencode } from "@opencode-ai/sdk" - -export interface SelectedModel { - providerID: string - modelID: string -} - -// Custom structure for UI compatibility -export type MessageWithParts = { - info: Opencode.Message - parts: Opencode.Part[] -} diff --git a/tsconfig.app.json b/tsconfig.app.json deleted file mode 100644 index 2cb7bcb..0000000 --- a/tsconfig.app.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2022", - "useDefineForClassFields": true, - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["src"] -} diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index ddb146b..0000000 --- a/vite.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import path from "path" -import tailwindcss from "@tailwindcss/vite" -import react from "@vitejs/plugin-react" -import { defineConfig } from "vite" - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react(), tailwindcss()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, - // server: { - // proxy: { - // "/api": { - // target: "http://localhost:4096", - // changeOrigin: true, - // rewrite: (path) => path.replace(/^\/api/, ""), - // }, - // }, - // }, -})