diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..fd83f093 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,36 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "npx -y @dotcontext/cli@latest hook dispatch --source claude-code" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "^Write$|^Edit$|^Bash$", + "hooks": [ + { + "type": "command", + "command": "npx -y @dotcontext/cli@latest hook dispatch --source claude-code" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "npx -y @dotcontext/cli@latest hook dispatch --source claude-code" + } + ] + } + ] + } +} diff --git a/.context/docs/README.md b/.context/docs/README.md index 5558b5ba..d6ff3473 100644 --- a/.context/docs/README.md +++ b/.context/docs/README.md @@ -12,6 +12,7 @@ Welcome to the `@dotcontext/cli` repository knowledge base. Start with the proje | Tooling & Productivity | [`tooling.md`](./tooling.md) | CLI scripts, IDE configs, automation workflows | | Harness Split Foundation | [`harness-split-foundation.md`](./harness-split-foundation.md) | Branch changes, package boundary decisions, harness engineering alignment | | Harness Roadmap | [`harness-roadmap.md`](./harness-roadmap.md) | Harness engineering capabilities, sequencing, and product direction | +| Web Interface Architecture | [`web-interface-architecture.md`](./web-interface-architecture.md) | Phase 1 API/SSE contract and ADRs for the `src/web` harness adapter | ## Q&A diff --git a/.context/docs/development-workflow.md b/.context/docs/development-workflow.md index 77e0c0c0..7149e13d 100644 --- a/.context/docs/development-workflow.md +++ b/.context/docs/development-workflow.md @@ -74,7 +74,9 @@ npm install | Command | Description | | ----------------- | ------------------------------------------------ | | `npm run dev` | Run CLI from source using tsx (fast, no build step) | +| `npm run dev:web` | Run the web API and Vite UI together for dashboard development | | `npm run build` | Compile TypeScript to `dist/` via tsc | +| `npm run build:web-ui` | Build the React dashboard into `web-ui/dist` | | `npm start` | Run the compiled CLI from `dist/index.js` | | `npm test` | Run the full Jest test suite | @@ -87,6 +89,26 @@ npm install 5. Run `npm run build` to confirm the TypeScript compiles cleanly. 6. Commit, push, and open a pull request against `main`. +### Web dashboard loop + +Use this loop when changing `src/web` or `web-ui/`: + +```bash +npm install +npm --prefix web-ui install +npm run dev:web +``` + +`dev:web` starts the API via `dotcontext web --api-only --no-open` on `127.0.0.1:4317` and Vite on `localhost:5173`. Open the Vite URL while developing. For production-style local testing, run: + +```bash +npm run build:web-ui +npm run build +node dist/index.js web --no-open +``` + +Because `@dotcontext/cli` bundles `web-ui/dist`, packaging-related web changes must also pass `npm run build:packages` and `npm run smoke:packages`. + ### Environment variables The CLI supports locale overrides via `DOTCONTEXT_LANG` (`en` or `pt-BR`). diff --git a/.context/docs/tooling.md b/.context/docs/tooling.md index 25e9d6a1..efc98561 100644 --- a/.context/docs/tooling.md +++ b/.context/docs/tooling.md @@ -36,7 +36,11 @@ All scripts are defined in `package.json`: | Script | Command | Description | | ------------------- | ------------------------------------------------- | ---------------------------------------- | | `dev` | `tsx src/index.ts` | Run CLI from source (no build step) | +| `dev:web` | `node scripts/dev-web.js` | Run the web API and Vite UI together | +| `dev:web-api` | `tsx src/index.ts web --api-only --no-open` | Run only the dashboard REST + SSE API | +| `dev:web-ui` | `npm --prefix web-ui run dev` | Run only the Vite React dashboard | | `build` | `tsc` | Compile TypeScript to `dist/` | +| `build:web-ui` | `npm --prefix web-ui install && npm --prefix web-ui run build` | Build the React dashboard into `web-ui/dist` | | `start` | `node dist/index.js` | Run compiled CLI | | `test` | `jest` | Run the full test suite | | `prepublishOnly` | `npm run build` | Ensure a fresh build before publish | @@ -62,6 +66,20 @@ npm test npm run build && npm start -- --help ``` +### Web dashboard workflow + +```bash +# Run API + Vite together +npm run dev:web + +# Build and serve the production UI locally +npm run build:web-ui +npm run build +node dist/index.js web --no-open +``` + +The source dev loop uses `dotcontext web --api-only` so the backend can run without an existing `web-ui/dist`. The package workflow rebuilds `web-ui/dist`, copies it into `.release/packages/cli/web-ui/dist`, and verifies it in `npm run smoke:packages`. + ### Release workflow ```bash diff --git a/.context/docs/web-interface-architecture.md b/.context/docs/web-interface-architecture.md new file mode 100644 index 00000000..848a5dd4 --- /dev/null +++ b/.context/docs/web-interface-architecture.md @@ -0,0 +1,144 @@ +--- +type: doc +name: web-interface-architecture +description: Phase 1 architecture decisions and API/SSE contract for the new src/web harness adapter (dotcontext web dashboard) +category: architecture +generated: "2026-06-30" +status: filled +scaffoldVersion: "2.0.0" +--- + +# Web Interface Architecture (Phase 1) + +This document is the Phase 1 deliverable for the [Web Interface plan](../plans/web-interface.md): it defines the `src/web` boundary contract and records the two architecture decisions (ADRs) required before implementation starts in Phase 3/4. No implementation code is introduced by this document. + +## 1. Boundary Recap + +`src/web` becomes a fifth boundary alongside `cli`, `harness`, `mcp`, and `integrations`: + +```text +cli -> harness <- mcp + ^ + | + web +``` + +- `src/web` depends only on `src/harness` (application services), exactly like `src/mcp`. +- `src/web` never imports from `src/cli` or `src/mcp`. +- `src/web` introduces no new domain logic — every endpoint is a thin transport wrapper over an existing (or, where noted below, a small new) `src/harness/application` service. +- `web-ui/` (the React + Vite SPA, see ADR-2) is a separate npm project; it talks to `src/web` only over HTTP/SSE, never by importing TypeScript from `src/harness` or `src/web` directly. + +## 2. ADR-1: Server Runtime + +**Decision:** Plain `node:http` plus a small hand-written router living in `src/web` (e.g. `src/web/server.ts`, `src/web/router.ts`). No Express, Fastify, or similar framework dependency. + +**Status:** Accepted (recorded on the plan via `plan.recordDecision`). + +**Context:** +- The repository has zero HTTP framework dependencies today (see `package.json`); `src/mcp` is built directly on `@modelcontextprotocol/sdk` transport primitives without an extra web framework on top. +- `CLAUDE.md`/`AGENTS.md` favor keeping `harness` reusable and adapters thin; a full framework would add a large dependency surface for what is a handful of read-mostly JSON routes plus one SSE stream. +- The route set is small and stable (see contract in §4): exact-path and single-param routes (`/api/sessions/:id`, `/api/sessions/:id/traces`, etc.), which a ~100-150 line router can match without a framework. + +**Decision detail:** +- `src/web/server.ts` exports `startWebServer(options: { repoPath: string; port?: number; host?: string })` that creates an `http.Server`, binds to `127.0.0.1` by default (never a remote interface unless explicitly overridden — see Risk Assessment in the plan), and delegates requests to the router. +- The router matches `(method, pathname)` against a small static table of route definitions (path template + handler), extracting `:param` segments itself; this mirrors the existing `src/mcp/gateway/*.ts` pattern of one handler module per resource area (`docs.ts`, `skills.ts`, `agents.ts`, `sessions.ts`, `workflow.ts`, `events.ts`). +- JSON responses use one envelope for the whole API: `{ "data": }` on success, `{ "error": { "message": string } }` with a non-2xx status on failure — this is a new, consistent envelope (existing harness services return ad hoc `{ success, ... }` shapes meant for MCP tool results, not for an HTTP API). +- Static asset serving (the built `web-ui/dist`) is a second, smaller responsibility of the same server: any request that doesn't match an `/api/*` route falls through to a static file handler, enabling SPA client-side routing via a catch-all `index.html` fallback. +- The runtime watcher (§4.6) needs a recursive, debounced file watcher across `.context/runtime/**`; plain `fs.watch({ recursive: true })` is not reliably available on Linux across supported Node versions, so `chokidar` is added as the one new dependency for `src/web`. It must be declared as a real `dependencies` entry in the root `package.json` (not `devDependencies`) since `src/web` ships inside `@dotcontext/cli`/`@dotcontext/harness` and `scripts/build-package-bundles.js` only copies `dependencies` into published package bundles; the `@dotcontext/harness` bundle's explicit dependency allowlist must also be updated to include `chokidar`, or `GET /api/events` will throw at runtime for installed packages. This is a required Phase 3 setup step, not optional polish. + +**Alternatives considered:** +- *Express* — most familiar, but pulls in a sizeable dependency tree (body-parser, etc.) for a route count this small, and the project has otherwise stayed dependency-light (see `package.json`). +- *Fastify* — faster and schema-validated, but again a new dependency + plugin ecosystem disproportionate to ~10 routes, and would push validation logic into `src/web` that should arguably stay in `src/harness` if it ever needs to exist. +- *Plain `node:http` with no router abstraction at all* — rejected only because hand-matching ~10 paths inline in one file would get unreadable; a ~100 line internal router keeps it readable without adding a dependency. + +## 3. ADR-2: Frontend Project Location + +**Decision:** A sibling `web-ui/` directory at the repository root, with its own `package.json`, `vite.config.ts`, and `node_modules` — not `src/web/ui`. + +**Status:** Accepted (recorded on the plan via `plan.recordDecision`). + +**Context:** +- The repository already has a precedent for this: `docs/` is a fully independent Astro + Starlight project at the repo root (`docs/package.json`, own `astro` toolchain), not nested inside `src/`. +- The root `tsconfig.json`/`jest.config.js`/`npm run build` pipeline targets Node/CLI output (`tsc` only); Vite/React needs a browser-targeted `tsconfig`, JSX support, and a completely different bundler. Nesting it under `src/web/ui` risks it being picked up by `tsc`'s root build or `npm run build:packages`' package-bundle step, which currently assumes everything under `src/` compiles with the single root `tsc` config. +- Keeping `web-ui/` self-contained means its dependencies (`react`, `react-dom`, `vite`, `@vitejs/plugin-react`, a markdown renderer) never enter the dependency tree of the published `@dotcontext/cli`/`@dotcontext/harness`/`@dotcontext/mcp` packages — only the *build output* (`web-ui/dist`) is consumed at runtime, copied/served by `src/web`. + +**Decision detail:** +- `web-ui/` ships a `dev` script (Vite dev server with a proxy to the `src/web` API port) and a `build` script (`vite build` → `web-ui/dist`). +- A new root script, `npm run build:web-ui` (`cd web-ui && npm install && npm run build`), is added in Phase 4 so `web-ui/dist` exists before `src/web`'s static handler needs it; it is not part of `npm run build` (the Node/CLI build) to avoid forcing every contributor to install browser tooling. +- `src/web`'s static file handler reads from `web-ui/dist` at runtime; it does not import any `web-ui` TypeScript source. The `dotcontext web` CLI command (Phase 3) documents that `web-ui/dist` must exist (built via `npm run build:web-ui`) before `dotcontext web` serves the production build; `dotcontext web --api-only` skips the dist check so Vite can serve the UI during development. +- Packaging: `web-ui/dist` is treated as a build artifact bundled into the `@dotcontext/cli` package's `files` (similar to how `dist/**/*` is already listed in `package.json`). `npm run build:packages` rebuilds the UI and copies it into `.release/packages/cli/web-ui/dist`; `npm run smoke:packages` verifies the installed CLI bundle contains `web-ui/dist/index.html`. + +**Alternatives considered:** +- *`src/web/ui` (nested TypeScript source)* — rejected: would require either a second `tsconfig` scoped to that subtree (fragile, easy to misconfigure) or polluting the root `tsconfig`/`jest.config.js` with browser globals and JSX, both of which add real risk to the existing single-`tsc`-build pipeline described in `ARCHITECTURE.md`. +- *A full separate package/repo for `web-ui`* — rejected for now as premature; the plan's own follow-up list already flags "bundled in `@dotcontext/cli` vs. a future `@dotcontext/web` package" as an open question for Phase 6, so the simplest viable structure (sibling directory, not yet its own publishable package) is chosen for Phase 1-4. + +## 4. API + SSE Contract + +All routes are mounted under `/api`. Every handler is implemented in `src/web/routes/*.ts` and calls into the listed `src/harness/application` service — no new business logic, only request parsing + response shaping. Response envelope: `{ "data": }` (200) or `{ "error": { "message": string } }` (4xx/5xx). + +### 4.1 Docs — `GET /api/docs`, `GET /api/docs/:name` + +| Route | Harness service call | Response `data` | +| --- | --- | --- | +| `GET /api/docs` | **New, small** `HarnessDocsService.list()` (Phase 3 addition, mirrors `HarnessSkillsService.list()`; no equivalent currently exists for `.context/docs/*.md`) | `Array<{ name: string; title: string; description?: string; category?: string; status: 'filled' \| 'unfilled' }>` | +| `GET /api/docs/:name` | `HarnessDocsService.getContent(name)` | `{ name: string; frontMatter: object; content: string }` | + +This is the one place Phase 1 identifies a genuinely new (but tiny, read-only) service: today only `skillsService.list()`/`getContent()` exist for skills; docs have no equivalent listing service. `HarnessDocsService` should be added under `src/harness/application/docs/` in Phase 3, following the exact shape of `HarnessSkillsService`. + +### 4.2 Skills — `GET /api/skills`, `GET /api/skills/:slug` + +| Route | Harness service call | Response `data` | +| --- | --- | --- | +| `GET /api/skills?content=true` | `HarnessSkillActionService.execute({ action: 'list', includeContent })` (`HarnessSkillsService.list`) | `{ success, skills: Array<{ slug, name, description, phases, isBuiltIn, content? }> }` | +| `GET /api/skills/:slug` | `HarnessSkillActionService.execute({ action: 'getContent', skillSlug })` | Skill content payload | + +### 4.3 Agents — `GET /api/agents`, `GET /api/agents/:type` + +| Route | Harness service call | Response `data` | +| --- | --- | --- | +| `GET /api/agents` | `HarnessAgentActionService.execute({ action: 'discover' })` | `{ success, totalAgents, builtInCount, customCount, agents: { builtIn, custom } }` | +| `GET /api/agents/:type` | `HarnessAgentActionService.execute({ action: 'getInfo', agentType })` + `execute({ action: 'getDocs', agent })` | `{ info, docs }` | + +### 4.4 Sessions — `GET /api/sessions`, `GET /api/sessions/:id`, `.../traces`, `.../artifacts`, `.../checkpoints` + +Backed directly by `HarnessRuntimeStateService` (constructed once per `src/web` process with `{ repoPath }`): + +| Route | Harness service call | Response `data` | +| --- | --- | --- | +| `GET /api/sessions` | `runtimeStateService.listSessions()` | `HarnessSessionRecord[]` | +| `GET /api/sessions/:id` | `runtimeStateService.getSession(id)` | `HarnessSessionRecord` | +| `GET /api/sessions/:id/traces` | `runtimeStateService.listTraces(id)` | `HarnessTraceRecord[]` | +| `GET /api/sessions/:id/artifacts` | `runtimeStateService.listArtifacts(id)` | `HarnessArtifactRecord[]` | +| `GET /api/sessions/:id/checkpoints` | `runtimeStateService.listCheckpoints(id)` | `HarnessSessionCheckpoint[]` | + +These are read-only in Phase 1-5; no write routes (create/checkpoint/append) are exposed over HTTP for the initial dashboard scope. + +### 4.5 Workflow — `GET /api/workflow/status`, `GET /api/workflow/guide`, `GET /api/workflow/plans`, `GET /api/workflow/plans/:slug`, `GET /api/workflow/harness` + +| Route | Harness service call | Response `data` | +| --- | --- | --- | +| `GET /api/workflow/status` | `WorkflowService.getStatus()` + `WorkflowService.getSummary()` | `{ status: PrevcStatus; summary: WorkflowSummary }` | +| `GET /api/workflow/guide` | `WorkflowGuideService.guide({ intent: 'session_start' })` | `WorkflowGuideResult` (phase, next steps, recommended skills) | +| `GET /api/workflow/plans` | `HarnessPlansService.getLinked()` | `{ success, plans: { active, completed } }` | +| `GET /api/workflow/plans/:slug` | `HarnessPlansService.getDetails(slug)` | Linked plan phases/steps/decisions | +| `GET /api/workflow/harness` | `HarnessSessionFacade.getHarnessStatus(workflowName)` | `WorkflowHarnessStatus` (binding, session, sensor runs, task contracts, handoffs, completion check) — this is the primary feed for the "current harness session" panel called out in the plan's success signal | + +### 4.6 Live Updates — `GET /api/events` (SSE) + +- `text/event-stream` response; on connect, immediately emits a `hello` event with the current server timestamp. +- A `chokidar` watcher (see ADR-1) observes `.context/runtime/**` (sessions, workflows, contracts, evaluations) with `awaitWriteFinish` debouncing (~300ms) and emits a `runtime-change` event per debounced batch: `{ "paths": string[] }` (repo-relative paths that changed). +- The payload is intentionally coarse — it tells the client *something* under `.context/runtime` changed, not which specific resource. Per the plan's risk mitigation, the frontend must treat any event (and reconnect) as "refetch the active view's REST data," never trust the SSE payload as authoritative state. This keeps `src/web` from having to reconstruct fine-grained diffs. +- One watcher instance is shared across all connected SSE clients; each client gets its own `http.ServerResponse` kept open and written to, cleaned up on `close`. + +### 4.7 Error & Security Notes + +- All routes are `GET` only in this phase (read-only dashboard). Any future write route (e.g. plan approval) must go through `HarnessPolicyService.authorize(...)` exactly like the MCP gateway does, and is out of scope for Phases 3-4 unless explicitly added back into this contract. +- Default bind address is `127.0.0.1`; binding elsewhere requires an explicit `--host` flag and should log a warning, per the plan's risk assessment ("no auth, localhost-only" is the default security posture). + +## 5. Phase 2 Review Notes + +Reviewed against `ARCHITECTURE.md`/`CLAUDE.md` boundary rules and `src/mcp/gateway` precedent. **Outcome: Approved with notes.** + +- No `src/cli`/`src/mcp` imports proposed; all domain logic stays in `src/harness`; `HarnessDocsService` naming matches the existing `HarnessService` convention. +- Fixed during review: ADR-1's chokidar dependency placement (§2) — now a `dependencies` entry, not `devDependencies`, with the harness package-bundle allowlist called out explicitly. +- Carried into Phase 3 as implementation notes (non-blocking): (a) `HarnessDocsService.getContent` must reuse the existing `src/utils/pathSecurity.ts` path-validation pattern (same as `HarnessSkillsService`) since `:name`/`:slug` route params are now reachable over plain local HTTP; (b) consider a Host-header check or strict CORS policy as a defense-in-depth note against DNS-rebinding, even though "localhost-only, no auth" remains the accepted default posture; (c) the plan's "Reusable services" list should be updated to mention the new `HarnessDocsService` deliverable. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a5a35203..8908f5ea 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -9,16 +9,19 @@ Dotcontext now treats harness engineering as a first-class runtime concern. - `cli` is the operator-facing interface - `harness` is the reusable runtime and domain layer - `mcp` is the transport adapter that exposes the harness to AI tools +- `web` is the local HTTP/SSE adapter that serves the browser dashboard ```mermaid flowchart LR User["Human / Operator"] --> CLI["dotcontext/cli"] AITool["AI Tool / MCP Client"] --> MCP["dotcontext/mcp"] HookHost["Hook Host / Extension"] --> Hooks["dotcontext/harness hooks"] + Browser["Local Browser"] --> Web["dotcontext/web"] CLI --> H["dotcontext/harness"] MCP --> H Hooks --> H + Web --> H H --> WF["Workflow Runtime"] H --> RS["Runtime State"] @@ -43,16 +46,19 @@ The current architecture is intentionally asymmetric: ```text cli -> harness <- mcp + ^ + web ``` That means: - `harness` does not depend on `cli` - `harness` does not depend on `mcp` -- `cli` and `mcp` are adapters over the same runtime +- `harness` does not depend on `web` +- `cli`, `mcp`, `integrations`, and `web` are adapters over the same runtime - transport concerns stay outside the core domain -This keeps the harness reusable for future adapters such as HTTP, workers, or SDKs. +This keeps the harness reusable for adapters such as the current local HTTP dashboard, future workers, or SDKs. ## Runtime Responsibilities @@ -148,6 +154,8 @@ flowchart TD Root --> CLI["src/cli"] Root --> Harness["src/harness"] Root --> MCP["src/mcp"] + Root --> Web["src/web"] + Root --> WebUI["web-ui"] Root --> Integrations["src/integrations"] Root --> Shared["src/shared"] Root --> Scripts["scripts"] @@ -156,6 +164,8 @@ flowchart TD CLI --> CLIAdapters["adapters / commands / services / ui"] Harness --> HarnessBoundary["application / domain / ports / adapters"] MCP --> MCPBoundary["server / gateway / logging / resources"] + Web --> WebBoundary["node:http server / routes / SSE events"] + WebUI --> WebUIBoundary["React + Vite SPA"] Integrations --> HostAdapters["claude-code / codex / pi-dev hooks"] Shared --> SharedCore["fs / context / registry / system"] @@ -171,7 +181,7 @@ flowchart TD Context --> Docs["docs / plans / agents / skills"] ``` -The canonical source paths are `src/cli`, `src/harness`, `src/mcp`, `src/integrations`, and `src/shared`. `src/services` is not a target architecture folder. During migration, old deep imports should be redirected through package exports, local path aliases, or short-lived release-branch shims, but the source tree should not keep `src/services` as a permanent compatibility layer. +The canonical source paths are `src/cli`, `src/harness`, `src/mcp`, `src/web`, `src/integrations`, and `src/shared`. `web-ui/` is a sibling browser app whose production `dist/` is served by `src/web`; it does not import from `src/harness` directly. `src/services` is not a target architecture folder. During migration, old deep imports should be redirected through package exports, local path aliases, or short-lived release-branch shims, but the source tree should not keep `src/services` as a permanent compatibility layer. ## Packaging Model @@ -212,6 +222,7 @@ The current consolidated architecture already supports: - replayable execution history - clustered failure datasets - local packaging and smoke validation for `cli`, `harness`, and `mcp` +- a bundled local web dashboard served by `dotcontext web` The next layer of evolution is not more boundary work. It is product depth on top of this runtime: diff --git a/CHANGELOG.md b/CHANGELOG.md index 242a3b11..aaa11246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added the bundled `dotcontext web` dashboard for local browser inspection of docs, skills, agents, harness sessions, traces, artifacts, checkpoints, linked plans, and PREVC workflow status. +- Added web development scripts: `npm run dev:web`, `npm run dev:web-api`, `npm run dev:web-ui`, and the `dotcontext web --api-only` mode for Vite-backed development. + +### Changed + +- Package builds now rebuild and include `web-ui/dist` in the `@dotcontext/cli` bundle, with smoke coverage that verifies the installed CLI package contains the web dashboard assets. + ## [1.1.1] - 2026-06-27 ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 628219cc..6db33273 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,9 @@ Useful commands: ```bash npm run dev +npm run dev:web npm run build +npm run build:web-ui npm test -- --runInBand npm run build:packages npm run smoke:packages @@ -43,6 +45,38 @@ npm run smoke:packages 5. Run `npm run build` and `npm test -- --runInBand` before opening a PR. 6. If the change affects packaging, also run `npm run build:packages` and `npm run smoke:packages`. +## Web Dashboard Development + +The local dashboard has two parts: + +- `src/web` — the Node `http` REST + SSE adapter over the harness runtime +- `web-ui/` — the React + Vite SPA served by `src/web` in production + +For day-to-day UI work, install both dependency sets and run the combined dev script: + +```bash +npm install +npm --prefix web-ui install +npm run dev:web +``` + +`dev:web` starts `dotcontext web --api-only --no-open` on `127.0.0.1:4317` and Vite on `localhost:5173`. Open the Vite URL while developing. If you only need one side, use `npm run dev:web-api` or `npm run dev:web-ui`. + +For a local production-style run: + +```bash +npm run build:web-ui +npm run build +node dist/index.js web --no-open +``` + +The published CLI bundle includes `web-ui/dist`. Any change that affects `dotcontext web`, `src/web`, `web-ui/`, package manifests, or release scripts must run: + +```bash +npm run build:packages +npm run smoke:packages +``` + ## Documentation Expectations The public docs that matter most are: diff --git a/README.md b/README.md index 73e31c73..7f4f2f8a 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ Dotcontext is three things at once: - a `.context/` convention for durable project knowledge - a harness runtime that governs how agents execute work -- CLI, MCP, and host integration surfaces that expose the same runtime to humans, AI tools, and lifecycle hooks +- CLI, MCP, host integration, and web dashboard surfaces that expose the same runtime to humans, AI tools, lifecycle hooks, and a local browser UI -The repository is organized around one runtime and five package surfaces: +The repository is organized around one runtime, five package surfaces, and a bundled local web dashboard: ```text cli -> harness <- mcp @@ -36,6 +36,7 @@ cli -> harness <- mcp | MCP | `@dotcontext/mcp` | MCP transport adapter and installer for AI tools | | Integrations | `@dotcontext/integrations` | Host hook adapters and event mappers for Claude Code, Codex CLI, and Pi | | Pi extension | `@dotcontext/pi` | Pi npm extension for in-process lifecycle hooks | +| Web dashboard | bundled in `@dotcontext/cli` | Local browser dashboard for docs, skills, agents, sessions, traces, and PREVC workflow status | For the full system view, see [ARCHITECTURE.md](./ARCHITECTURE.md). @@ -105,6 +106,32 @@ npx -y @dotcontext/cli@latest Context creation, AI-generated fills, and plan scaffolding are MCP-first. The standalone CLI does not provide the old direct `init`, `fill`, `plan`, `update`, or `analyze` command flow. +### Path 3: Local web dashboard + +Use the bundled dashboard when you want to inspect a repository's `.context` state in a browser while CLI, MCP, or hook sessions are running. + +```bash +npx -y @dotcontext/cli@latest web +``` + +By default it binds to `127.0.0.1:4317`, serves the built React UI, and opens the browser. The dashboard exposes read-only REST endpoints plus an SSE stream for live refreshes. + +For repository development with Vite HMR: + +```bash +npm install +npm --prefix web-ui install +npm run dev:web +``` + +For a local production build from source: + +```bash +npm run build:web-ui +npm run build +node dist/index.js web --no-open +``` + ## Core Concepts ### Shared Context diff --git a/docs/src/content/docs/en/guides/using-the-cli.md b/docs/src/content/docs/en/guides/using-the-cli.md index 406d8064..9f626e63 100644 --- a/docs/src/content/docs/en/guides/using-the-cli.md +++ b/docs/src/content/docs/en/guides/using-the-cli.md @@ -150,6 +150,24 @@ dotcontext admin skill export --preset all dotcontext admin skill export --preset claude --force ``` +### Open the web dashboard + +`web` starts a local browser dashboard for the current repository's `.context` state. + +```bash +dotcontext web +dotcontext web --no-open +dotcontext web --port 4399 --no-open +``` + +The installed CLI serves the bundled React UI. When developing dotcontext itself, use the source dev loop: + +```bash +npm run dev:web +``` + +That starts the API with `dotcontext web --api-only --no-open` and Vite with HMR. See [Web dashboard](/guides/web-dashboard/) for the full dev and packaging workflow. + ## Integration setup from the CLI The CLI is also how you wire MCP, lifecycle hooks, and the Pi extension into your editor or agent. The interactive **Integrations** submenu exposes: diff --git a/docs/src/content/docs/en/guides/web-dashboard.md b/docs/src/content/docs/en/guides/web-dashboard.md new file mode 100644 index 00000000..4c050e49 --- /dev/null +++ b/docs/src/content/docs/en/guides/web-dashboard.md @@ -0,0 +1,91 @@ +--- +title: Web dashboard +description: Run the local dotcontext browser dashboard, use Vite during development, and package the built UI with the CLI. +sidebar: + order: 8 +--- + +The dotcontext web dashboard is a local, read-only browser UI over the harness runtime. It shows docs, skills, agents, sessions, traces, artifacts, checkpoints, linked plans, and PREVC workflow status while CLI, MCP, or hook sessions are running. + +## Run from the installed CLI + +```bash +npx -y @dotcontext/cli@latest web +``` + +By default, `dotcontext web` binds to `127.0.0.1:4317`, serves the bundled React UI, starts the REST + SSE API, and opens the browser. Use `--no-open` when running in a terminal-only environment. + +```bash +dotcontext web --no-open +dotcontext web --port 4399 --no-open +``` + +::: caution +The dashboard has no authentication. It binds to `127.0.0.1` by default. Only pass `--host` for a trusted local network. +::: + +## Development mode + +When working in the dotcontext repository, use Vite for the UI and the source CLI for the API: + +```bash +npm install +npm --prefix web-ui install +npm run dev:web +``` + +This starts: + +| Process | URL | Purpose | +| --- | --- | --- | +| `dotcontext web --api-only --no-open` | `http://127.0.0.1:4317` | REST + SSE API | +| Vite | `http://localhost:5173` | React UI with HMR | + +Open the Vite URL. Vite proxies `/api/*` and `/api/events` to the API process. + +To run the processes separately: + +```bash +npm run dev:web-api +npm run dev:web-ui +``` + +If the API runs on a different port: + +```bash +VITE_API_PROXY_TARGET=http://127.0.0.1:4399 npm run dev:web-ui +``` + +## Local production build + +Build the static UI and serve it through the compiled CLI: + +```bash +npm run build:web-ui +npm run build +node dist/index.js web --no-open +``` + +Package validation also rebuilds and copies `web-ui/dist` into the CLI bundle: + +```bash +npm run build:packages +npm run smoke:packages +``` + +`smoke:packages` verifies that `.release/packages/cli/web-ui/dist/index.html` exists, so the installed `@dotcontext/cli` package can run `dotcontext web` without the source `web-ui/` project. + +## API surface + +The dashboard API is mounted under `/api`: + +| Area | Routes | +| --- | --- | +| Docs | `/api/docs`, `/api/docs/:name` | +| Skills | `/api/skills`, `/api/skills/:slug` | +| Agents | `/api/agents`, `/api/agents/:type` | +| Sessions | `/api/sessions`, `/api/sessions/:id`, `/traces`, `/artifacts`, `/checkpoints` | +| Workflow | `/api/workflow/status`, `/guide`, `/plans`, `/plans/:slug`, `/harness` | +| Events | `/api/events` | + +`/api/events` is an SSE stream. The UI treats every event as a signal to refetch the current REST data; the event payload is not authoritative state. diff --git a/docs/src/content/docs/en/index.mdx b/docs/src/content/docs/en/index.mdx index 5249404b..e6812ee7 100644 --- a/docs/src/content/docs/en/index.mdx +++ b/docs/src/content/docs/en/index.mdx @@ -54,6 +54,7 @@ cli -> harness <- mcp + diff --git a/docs/src/content/docs/en/reference/cli-commands.md b/docs/src/content/docs/en/reference/cli-commands.md index 64a5e41d..a3490052 100644 --- a/docs/src/content/docs/en/reference/cli-commands.md +++ b/docs/src/content/docs/en/reference/cli-commands.md @@ -53,6 +53,7 @@ These commands appear in `dotcontext --help` and are the ones most operators use | [`reverse-sync`](#reverse-sync) | Scan AI tool directories and import rules, agents, and skills into `.context/` | | [`export-rules`](#export-rules) | Export `.context/docs/` rules to AI tool directories | | [`mcp`](#mcp) | Start the MCP server (stdio transport) | +| [`web`](#web) | Start the local web dashboard and REST + SSE API | | [`mcp:install`](#mcpinstall) | Install MCP server config into supported AI tools | | [`mcp:uninstall`](#mcpuninstall) | Remove dotcontext MCP server config from supported AI tools | @@ -182,6 +183,30 @@ dotcontext mcp --repo-path /path/to/repo There is no separate global binary for the server in the published CLI package — the `dotcontext-mcp` bin only exists in the isolated `@dotcontext/mcp` package build. From the CLI, start the server with `dotcontext mcp`. See [Architecture](/about/architecture/) for how the surfaces are split. ::: +### web + +Start the local browser dashboard for the current repository. The command serves the bundled React UI plus the read-only REST + SSE API used by the dashboard. + +| Flag | Description | Default | +| --- | --- | --- | +| `-p, --port ` | Port to listen on | `4317` | +| `--host ` | Host to bind to | `127.0.0.1` | +| `--api-only` | Start only the REST + SSE API for Vite development | `false` | +| `--no-open` | Do not open the dashboard in a browser automatically | open by default | + +```bash +dotcontext web +dotcontext web --no-open +dotcontext web --port 4399 --no-open +dotcontext web --api-only --no-open +``` + +::: caution +The web dashboard has no authentication. It binds to `127.0.0.1` by default; use `--host` only on a trusted local network. +::: + +For development, prefer `npm run dev:web` from the repository root. See [Web dashboard](/guides/web-dashboard/). + ### mcp:install Install (or update) the MCP server configuration for a supported AI tool. Run with no tool name to pick interactively; pass a tool name to target it directly. See [Installing with MCP](/guides/using-with-mcp/) for the full list of supported clients and config paths. diff --git a/docs/src/content/docs/pt-br/guides/using-the-cli.md b/docs/src/content/docs/pt-br/guides/using-the-cli.md index 3df9e66a..a9019367 100644 --- a/docs/src/content/docs/pt-br/guides/using-the-cli.md +++ b/docs/src/content/docs/pt-br/guides/using-the-cli.md @@ -150,6 +150,24 @@ dotcontext admin skill export --preset all dotcontext admin skill export --preset claude --force ``` +### Abrir o dashboard web + +`web` inicia um dashboard local no navegador para o estado `.context` do repositório atual. + +```bash +dotcontext web +dotcontext web --no-open +dotcontext web --port 4399 --no-open +``` + +A CLI instalada serve a UI React empacotada. Ao desenvolver o próprio dotcontext, use o loop de desenvolvimento a partir do código-fonte: + +```bash +npm run dev:web +``` + +Isso inicia a API com `dotcontext web --api-only --no-open` e o Vite com HMR. Veja [Dashboard web](/pt-br/guides/web-dashboard/) para o fluxo completo de desenvolvimento e empacotamento. + ## Setup de integrações pela CLI A CLI também é como você conecta MCP, hooks de ciclo de vida e a extensão Pi ao seu editor ou agente. O submenu interativo **Integrações** oferece: diff --git a/docs/src/content/docs/pt-br/guides/web-dashboard.md b/docs/src/content/docs/pt-br/guides/web-dashboard.md new file mode 100644 index 00000000..3edfe452 --- /dev/null +++ b/docs/src/content/docs/pt-br/guides/web-dashboard.md @@ -0,0 +1,91 @@ +--- +title: Dashboard web +description: Rode o dashboard local do dotcontext no navegador, use Vite durante o desenvolvimento e empacote a UI buildada com a CLI. +sidebar: + order: 8 +--- + +O dashboard web do dotcontext é uma UI local e somente leitura sobre o runtime do harness. Ele mostra docs, skills, agents, sessões, traces, artefatos, checkpoints, planos linkados e status do workflow PREVC enquanto sessões CLI, MCP ou hooks estão rodando. + +## Rodar pela CLI instalada + +```bash +npx -y @dotcontext/cli@latest web +``` + +Por padrão, `dotcontext web` faz bind em `127.0.0.1:4317`, serve a UI React empacotada, inicia a API REST + SSE e abre o navegador. Use `--no-open` em ambientes só de terminal. + +```bash +dotcontext web --no-open +dotcontext web --port 4399 --no-open +``` + +::: caution +O dashboard não tem autenticação. Ele faz bind em `127.0.0.1` por padrão. Use `--host` somente em uma rede local confiável. +::: + +## Modo de desenvolvimento + +Ao trabalhar no repositório do dotcontext, use Vite para a UI e a CLI a partir do código-fonte para a API: + +```bash +npm install +npm --prefix web-ui install +npm run dev:web +``` + +Isso inicia: + +| Processo | URL | Propósito | +| --- | --- | --- | +| `dotcontext web --api-only --no-open` | `http://127.0.0.1:4317` | API REST + SSE | +| Vite | `http://localhost:5173` | UI React com HMR | + +Abra a URL do Vite. O Vite encaminha `/api/*` e `/api/events` para o processo da API. + +Para rodar os processos separadamente: + +```bash +npm run dev:web-api +npm run dev:web-ui +``` + +Se a API rodar em outra porta: + +```bash +VITE_API_PROXY_TARGET=http://127.0.0.1:4399 npm run dev:web-ui +``` + +## Build local de produção + +Build a UI estática e sirva pela CLI compilada: + +```bash +npm run build:web-ui +npm run build +node dist/index.js web --no-open +``` + +A validação de pacote também recompila e copia `web-ui/dist` para o bundle da CLI: + +```bash +npm run build:packages +npm run smoke:packages +``` + +`smoke:packages` verifica que `.release/packages/cli/web-ui/dist/index.html` existe, então o pacote instalado `@dotcontext/cli` consegue rodar `dotcontext web` sem o projeto-fonte `web-ui/`. + +## Superfície da API + +A API do dashboard fica em `/api`: + +| Área | Rotas | +| --- | --- | +| Docs | `/api/docs`, `/api/docs/:name` | +| Skills | `/api/skills`, `/api/skills/:slug` | +| Agents | `/api/agents`, `/api/agents/:type` | +| Sessões | `/api/sessions`, `/api/sessions/:id`, `/traces`, `/artifacts`, `/checkpoints` | +| Workflow | `/api/workflow/status`, `/guide`, `/plans`, `/plans/:slug`, `/harness` | +| Eventos | `/api/events` | + +`/api/events` é um stream SSE. A UI trata cada evento como sinal para recarregar os dados REST atuais; o payload do evento não é estado autoritativo. diff --git a/docs/src/content/docs/pt-br/index.mdx b/docs/src/content/docs/pt-br/index.mdx index 2c4dd98d..56484337 100644 --- a/docs/src/content/docs/pt-br/index.mdx +++ b/docs/src/content/docs/pt-br/index.mdx @@ -54,6 +54,7 @@ cli -> harness <- mcp + diff --git a/docs/src/content/docs/pt-br/reference/cli-commands.md b/docs/src/content/docs/pt-br/reference/cli-commands.md index 8edae30e..1c9fe250 100644 --- a/docs/src/content/docs/pt-br/reference/cli-commands.md +++ b/docs/src/content/docs/pt-br/reference/cli-commands.md @@ -53,6 +53,7 @@ Estes comandos aparecem em `dotcontext --help` e são os mais usados no dia a di | [`reverse-sync`](#reverse-sync) | Escaneia diretórios de ferramentas de IA e importa regras, agentes e skills para `.context/` | | [`export-rules`](#export-rules) | Exporta as regras de `.context/docs/` para diretórios de ferramentas de IA | | [`mcp`](#mcp) | Inicia o servidor MCP (transporte stdio) | +| [`web`](#web) | Inicia o dashboard web local e a API REST + SSE | | [`mcp:install`](#mcpinstall) | Instala a configuração do servidor MCP em ferramentas de IA suportadas | | [`mcp:uninstall`](#mcpuninstall) | Remove a configuração do servidor MCP dotcontext de ferramentas de IA suportadas | @@ -182,6 +183,30 @@ dotcontext mcp --repo-path /caminho/do/repo Não existe um binário global separado para o servidor no pacote `@dotcontext/cli` publicado — o bin `dotcontext-mcp` existe apenas no build isolado do pacote `@dotcontext/mcp`. A partir da CLI, inicie o servidor com `dotcontext mcp`. Veja [Arquitetura](/pt-br/about/architecture/) para entender como as superfícies são separadas. ::: +### web + +Inicia o dashboard local no navegador para o repositório atual. O comando serve a UI React empacotada e a API REST + SSE somente leitura usada pelo dashboard. + +| Flag | Descrição | Padrão | +| --- | --- | --- | +| `-p, --port ` | Porta para escutar | `4317` | +| `--host ` | Host para bind | `127.0.0.1` | +| `--api-only` | Inicia apenas a API REST + SSE para desenvolvimento com Vite | `false` | +| `--no-open` | Não abre o dashboard no navegador automaticamente | abre por padrão | + +```bash +dotcontext web +dotcontext web --no-open +dotcontext web --port 4399 --no-open +dotcontext web --api-only --no-open +``` + +::: caution +O dashboard web não tem autenticação. Ele faz bind em `127.0.0.1` por padrão; use `--host` somente em uma rede local confiável. +::: + +Para desenvolvimento, prefira `npm run dev:web` a partir da raiz do repositório. Veja [Dashboard web](/pt-br/guides/web-dashboard/). + ### mcp:install Instala (ou atualiza) a configuração do servidor MCP para uma ferramenta de IA suportada. Execute sem nome de ferramenta para escolher interativamente; passe um nome de ferramenta para mirá-la diretamente. Veja [Instalando com MCP](/pt-br/guides/using-with-mcp/) para a lista completa de clientes suportados e caminhos de config. diff --git a/package-lock.json b/package-lock.json index ad8e9dc7..40d32c31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@types/js-yaml": "^4.0.9", "boxen": "^5.1.2", "chalk": "^4.1.2", + "chokidar": "^4.0.3", "cli-progress": "^3.12.0", "commander": "^14.0.1", "fs-extra": "^11.3.2", @@ -2783,6 +2784,21 @@ "node": ">=10" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/ci-info": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", @@ -5677,6 +5693,19 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index 21cada27..d5c6e537 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "files": [ "dist/**/*", + "web-ui/dist/**/*", "README.md", "LICENSE", "prompts/**/*" @@ -28,7 +29,11 @@ }, "scripts": { "build": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\" && tsc", - "build:packages": "npm run build && node scripts/build-package-bundles.js", + "dev:web": "node scripts/dev-web.js", + "dev:web-api": "tsx src/index.ts web --api-only --no-open", + "dev:web-ui": "npm --prefix web-ui run dev", + "build:web-ui": "npm --prefix web-ui install && npm --prefix web-ui run build", + "build:packages": "npm run build && npm run build:web-ui && node scripts/build-package-bundles.js", "smoke:packages": "node scripts/smoke-package-bundles.js", "release:packages": "node scripts/release-packages.js", "release:packages:patch": "node scripts/release-packages.js patch", @@ -37,7 +42,7 @@ "dev": "tsx src/index.ts", "start": "node dist/index.js", "test": "jest", - "prepublishOnly": "npm run build", + "prepublishOnly": "npm run build && npm run build:web-ui", "version": "npm run build", "release": "npm version patch && npm publish --access public", "release:minor": "npm version minor && npm publish --access public", @@ -69,6 +74,7 @@ "@types/js-yaml": "^4.0.9", "boxen": "^5.1.2", "chalk": "^4.1.2", + "chokidar": "^4.0.3", "cli-progress": "^3.12.0", "commander": "^14.0.1", "fs-extra": "^11.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ec169b8..ceac0aab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,30 +8,24 @@ importers: .: dependencies: - '@ai-sdk/anthropic': - specifier: ^3.0.9 - version: 3.0.68(zod@4.3.6) - '@ai-sdk/google': - specifier: ^3.0.6 - version: 3.0.61(zod@4.3.6) - '@ai-sdk/openai': - specifier: ^3.0.7 - version: 3.0.52(zod@4.3.6) - '@inquirer/prompts': - specifier: ^7.10.1 - version: 7.10.1(@types/node@24.12.2) + '@clack/prompts': + specifier: ^1.6.0 + version: 1.6.0 '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.3.6) - ai: - specifier: ^6.0.19 - version: 6.0.156(zod@4.3.6) + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 boxen: specifier: ^5.1.2 version: 5.1.2 chalk: specifier: ^4.1.2 version: 4.1.2 + chokidar: + specifier: ^4.0.3 + version: 4.0.3 cli-progress: specifier: ^3.12.0 version: 3.12.0 @@ -42,14 +36,14 @@ importers: specifier: ^11.3.2 version: 11.3.4 glob: - specifier: ^10.4.5 - version: 10.5.0 + specifier: ^13.0.6 + version: 13.0.6 ignore: specifier: ^7.0.5 version: 7.0.5 - inquirer: - specifier: ^12.6.3 - version: 12.11.1(@types/node@24.12.2) + js-yaml: + specifier: ^4.1.1 + version: 4.3.0 ora: specifier: ^5.4.1 version: 5.4.1 @@ -73,9 +67,6 @@ importers: '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 - '@types/inquirer': - specifier: ^9.0.8 - version: 9.0.9 '@types/jest': specifier: ^30.0.0 version: 30.0.0 @@ -100,40 +91,6 @@ importers: packages: - '@ai-sdk/anthropic@3.0.68': - resolution: {integrity: sha512-BAd+fmgYoJMmGw0/uV+jRlXX60PyGxelA6Clp4cK/NI0dsyv9jOOwzQmKNaz2nwb+Jz7HqI7I70KK4XtU5EcXQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/gateway@3.0.95': - resolution: {integrity: sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/google@3.0.61': - resolution: {integrity: sha512-jEKU1Mjcy5CoicejdJQIzM0ntYwyXR8vtYgAZYriKaOuLAiAhiiU538++fGU3CC9HJH/mL1OfsCwMM3gFiCNsw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/openai@3.0.52': - resolution: {integrity: sha512-4Rr8NCGmfWTz6DCUvixn9UmyZcMatiHn0zWoMzI3JCUe9R1P/vsPOpCBALKoSzVYOjyJnhtnVIbfUKujcS39uw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider-utils@4.0.23': - resolution: {integrity: sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider@3.0.8': - resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==} - engines: {node: '>=18'} - '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -299,6 +256,14 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@clack/core@1.4.2': + resolution: {integrity: sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.6.0': + resolution: {integrity: sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==} + engines: {node: '>= 20.12.0'} + '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} @@ -470,140 +435,6 @@ packages: peerDependencies: hono: ^4 - '@inquirer/ansi@1.0.2': - resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} - engines: {node: '>=18'} - - '@inquirer/checkbox@4.3.2': - resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/confirm@5.1.21': - resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/core@10.3.2': - resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/editor@4.2.23': - resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/expand@4.0.23': - resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/external-editor@1.0.3': - resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@1.0.15': - resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} - engines: {node: '>=18'} - - '@inquirer/input@4.3.1': - resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/number@3.0.23': - resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/password@4.0.23': - resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/prompts@7.10.1': - resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/rawlist@4.1.11': - resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/search@3.2.2': - resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/select@4.4.2': - resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/type@3.0.10': - resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -727,10 +558,6 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@opentelemetry/api@1.9.0': - resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} - engines: {node: '>=8.0.0'} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -748,9 +575,6 @@ packages: '@sinonjs/fake-timers@15.3.0': resolution: {integrity: sha512-m2xozxSfCIxjDdvbhIWazlP2i2aha/iUmbl94alpsIbd3iLTfeXgfBVbwyWogB6l++istyGZqamgA/EcqYf+Bg==} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -772,9 +596,6 @@ packages: '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} - '@types/inquirer@9.0.9': - resolution: {integrity: sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==} - '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -787,6 +608,9 @@ packages: '@types/jest@30.0.0': resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} @@ -799,9 +623,6 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/through@0.0.33': - resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} - '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -906,20 +727,10 @@ packages: cpu: [x64] os: [win32] - '@vercel/oidc@3.1.0': - resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} - engines: {node: '>= 20'} - accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} - ai@6.0.156: - resolution: {integrity: sha512-uyi/5LYbugHQxZsR2PeAFOZEL4WqKkzZw4pv0nQvvdgxgVOsM7snOmGrYkp5fShxH/vnd08SXvHCVTX7oUW7xQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -965,6 +776,9 @@ packages: argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + babel-jest@30.3.0: resolution: {integrity: sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -993,6 +807,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1018,6 +836,10 @@ packages: brace-expansion@2.0.3: resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@5.0.7: + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} + engines: {node: 18 || 20 || >=22} + browserslist@4.28.2: resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1071,8 +893,9 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} ci-info@4.4.0: resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} @@ -1097,10 +920,6 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1292,9 +1111,18 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -1365,6 +1193,10 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -1435,15 +1267,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inquirer@12.11.1: - resolution: {integrity: sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} @@ -1642,6 +1465,10 @@ packages: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1656,9 +1483,6 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1688,6 +1512,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1728,6 +1556,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -1745,10 +1577,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} - napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1852,6 +1680,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} @@ -1908,6 +1740,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1935,13 +1771,6 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} - run-async@4.0.6: - resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} - engines: {node: '>=0.12.0'} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -1999,6 +1828,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -2212,10 +2044,6 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -2250,10 +2078,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} - engines: {node: '>=18'} - zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: @@ -2264,42 +2088,6 @@ packages: snapshots: - '@ai-sdk/anthropic@3.0.68(zod@4.3.6)': - dependencies: - '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) - zod: 4.3.6 - - '@ai-sdk/gateway@3.0.95(zod@4.3.6)': - dependencies: - '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) - '@vercel/oidc': 3.1.0 - zod: 4.3.6 - - '@ai-sdk/google@3.0.61(zod@4.3.6)': - dependencies: - '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) - zod: 4.3.6 - - '@ai-sdk/openai@3.0.52(zod@4.3.6)': - dependencies: - '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) - zod: 4.3.6 - - '@ai-sdk/provider-utils@4.0.23(zod@4.3.6)': - dependencies: - '@ai-sdk/provider': 3.0.8 - '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.6 - zod: 4.3.6 - - '@ai-sdk/provider@3.0.8': - dependencies: - json-schema: 0.4.0 - '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -2489,6 +2277,18 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@clack/core@1.4.2': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.6.0': + dependencies: + '@clack/core': 1.4.2 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + '@emnapi/core@1.9.2': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -2587,131 +2387,6 @@ snapshots: dependencies: hono: 4.12.12 - '@inquirer/ansi@1.0.2': {} - - '@inquirer/checkbox@4.3.2(@types/node@24.12.2)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/confirm@5.1.21(@types/node@24.12.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/core@10.3.2(@types/node@24.12.2)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/editor@4.2.23(@types/node@24.12.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/external-editor': 1.0.3(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/expand@4.0.23(@types/node@24.12.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/external-editor@1.0.3(@types/node@24.12.2)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/figures@1.0.15': {} - - '@inquirer/input@4.3.1(@types/node@24.12.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/number@3.0.23(@types/node@24.12.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/password@4.0.23(@types/node@24.12.2)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/prompts@7.10.1(@types/node@24.12.2)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@24.12.2) - '@inquirer/confirm': 5.1.21(@types/node@24.12.2) - '@inquirer/editor': 4.2.23(@types/node@24.12.2) - '@inquirer/expand': 4.0.23(@types/node@24.12.2) - '@inquirer/input': 4.3.1(@types/node@24.12.2) - '@inquirer/number': 3.0.23(@types/node@24.12.2) - '@inquirer/password': 4.0.23(@types/node@24.12.2) - '@inquirer/rawlist': 4.1.11(@types/node@24.12.2) - '@inquirer/search': 3.2.2(@types/node@24.12.2) - '@inquirer/select': 4.4.2(@types/node@24.12.2) - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/rawlist@4.1.11(@types/node@24.12.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/search@3.2.2(@types/node@24.12.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/select@4.4.2(@types/node@24.12.2)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.12.2 - - '@inquirer/type@3.0.10(@types/node@24.12.2)': - optionalDependencies: - '@types/node': 24.12.2 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2956,8 +2631,6 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@opentelemetry/api@1.9.0': {} - '@pkgjs/parseargs@0.11.0': optional: true @@ -2973,8 +2646,6 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@standard-schema/spec@1.1.0': {} - '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -3010,11 +2681,6 @@ snapshots: '@types/jsonfile': 6.1.4 '@types/node': 24.12.2 - '@types/inquirer@9.0.9': - dependencies: - '@types/through': 0.0.33 - rxjs: 7.8.2 - '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -3030,6 +2696,8 @@ snapshots: expect: 30.3.0 pretty-format: 30.3.0 + '@types/js-yaml@4.0.9': {} + '@types/jsonfile@6.1.4': dependencies: '@types/node': 24.12.2 @@ -3042,10 +2710,6 @@ snapshots: '@types/stack-utils@2.0.3': {} - '@types/through@0.0.33': - dependencies: - '@types/node': 24.12.2 - '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': @@ -3113,21 +2777,11 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vercel/oidc@3.1.0': {} - accepts@2.0.0: dependencies: mime-types: 3.0.2 negotiator: 1.0.0 - ai@6.0.156(zod@4.3.6): - dependencies: - '@ai-sdk/gateway': 3.0.95(zod@4.3.6) - '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) - '@opentelemetry/api': 1.9.0 - zod: 4.3.6 - ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -3168,6 +2822,8 @@ snapshots: dependencies: sprintf-js: 1.0.3 + argparse@2.0.1: {} + babel-jest@30.3.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 @@ -3222,6 +2878,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} baseline-browser-mapping@2.10.17: {} @@ -3266,6 +2924,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.7: + dependencies: + balanced-match: 4.0.4 + browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.17 @@ -3316,7 +2978,9 @@ snapshots: char-regex@1.0.2: {} - chardet@2.1.1: {} + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 ci-info@4.4.0: {} @@ -3334,8 +2998,6 @@ snapshots: cli-spinners@2.9.2: {} - cli-width@4.1.0: {} - cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -3537,8 +3199,18 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.0: {} + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -3620,6 +3292,12 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3686,18 +3364,6 @@ snapshots: inherits@2.0.4: {} - inquirer@12.11.1(@types/node@24.12.2): - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/prompts': 7.10.1(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) - mute-stream: 2.0.0 - run-async: 4.0.6 - rxjs: 7.8.2 - optionalDependencies: - '@types/node': 24.12.2 - ip-address@10.1.0: {} ipaddr.js@1.9.1: {} @@ -4074,6 +3740,10 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 + js-yaml@4.3.0: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} json-parse-even-better-errors@2.3.1: {} @@ -4082,8 +3752,6 @@ snapshots: json-schema-typed@8.0.2: {} - json-schema@0.4.0: {} - json5@2.2.3: {} jsonfile@6.2.0: @@ -4109,6 +3777,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.5.1: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4139,6 +3809,10 @@ snapshots: mimic-fn@2.1.0: {} + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.7 + minimatch@3.1.5: dependencies: brace-expansion: 1.1.13 @@ -4153,8 +3827,6 @@ snapshots: ms@2.1.3: {} - mute-stream@2.0.0: {} - napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -4243,6 +3915,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + path-to-regexp@8.4.2: {} picocolors@1.1.1: {} @@ -4293,6 +3970,8 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdirp@4.1.2: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -4320,12 +3999,6 @@ snapshots: transitivePeerDependencies: - supports-color - run-async@4.0.6: {} - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} @@ -4399,6 +4072,8 @@ snapshots: signal-exit@4.1.0: {} + sisteransi@1.0.5: {} + slash@3.0.0: {} source-map-support@0.5.13: @@ -4516,7 +4191,8 @@ snapshots: babel-jest: 30.3.0(@babel/core@7.29.0) jest-util: 30.3.0 - tslib@2.8.1: {} + tslib@2.8.1: + optional: true tsx@4.21.0: dependencies: @@ -4608,12 +4284,6 @@ snapshots: wordwrap@1.0.0: {} - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -4651,8 +4321,6 @@ snapshots: yocto-queue@0.1.0: {} - yoctocolors-cjs@2.1.3: {} - zod-to-json-schema@3.25.2(zod@4.3.6): dependencies: zod: 4.3.6 diff --git a/scripts/build-package-bundles.js b/scripts/build-package-bundles.js index 371f8452..b068f8eb 100644 --- a/scripts/build-package-bundles.js +++ b/scripts/build-package-bundles.js @@ -130,11 +130,12 @@ function buildBundles() { exports: { '.': './dist/cli/index.js' }, bin: { dotcontext: 'dist/index.js' }, dependencies: rootPkg.dependencies, - files: ['dist/**/*', 'prompts/**/*', 'README.md', 'LICENSE'], + files: ['dist/**/*', 'web-ui/dist/**/*', 'prompts/**/*', 'README.md', 'LICENSE'], } ), readme: loadTemplate('cli.README.md'), copyPrompts: true, + copyWebUiDist: true, }, { slug: 'harness', @@ -152,6 +153,7 @@ function buildBundles() { '@ai-sdk/openai', '@modelcontextprotocol/sdk', 'ai', + 'chokidar', 'fs-extra', 'glob', 'ignore', @@ -238,6 +240,13 @@ function buildBundles() { if (pkg.copyPrompts && fs.existsSync(path.join(repoRoot, 'prompts'))) { copyDir(path.join(repoRoot, 'prompts'), path.join(pkgRoot, 'prompts')); } + if (pkg.copyWebUiDist) { + const webUiDist = path.join(repoRoot, 'web-ui', 'dist'); + if (!fs.existsSync(webUiDist)) { + throw new Error('web-ui/dist does not exist. Run "npm run build:web-ui" first.'); + } + copyDir(webUiDist, path.join(pkgRoot, 'web-ui', 'dist')); + } for (const file of commonFiles) { copyFile(path.join(repoRoot, file), path.join(pkgRoot, file)); } diff --git a/scripts/dev-web.js b/scripts/dev-web.js new file mode 100644 index 00000000..e8c94ef2 --- /dev/null +++ b/scripts/dev-web.js @@ -0,0 +1,79 @@ +#!/usr/bin/env node + +const { spawn } = require('child_process'); + +const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + +const processes = [ + { + name: 'api', + command: npm, + args: ['run', 'dev:web-api'], + }, + { + name: 'ui', + command: npm, + args: ['run', 'dev:web-ui'], + }, +]; + +const children = []; +let shuttingDown = false; + +function prefix(name, chunk) { + const lines = chunk.toString().split(/\r?\n/); + for (const line of lines) { + if (line.length > 0) { + process.stdout.write(`[web:${name}] ${line}\n`); + } + } +} + +function stopAll(signal = 'SIGTERM') { + if (shuttingDown) { + return; + } + shuttingDown = true; + for (const child of children) { + if (!child.killed) { + child.kill(signal); + } + } +} + +function stopAllAndExit(signal, code) { + stopAll(signal); + setTimeout(() => process.exit(code), 500).unref(); +} + +for (const proc of processes) { + const child = spawn(proc.command, proc.args, { + stdio: ['ignore', 'pipe', 'pipe'], + env: process.env, + }); + + children.push(child); + + child.stdout.on('data', (chunk) => prefix(proc.name, chunk)); + child.stderr.on('data', (chunk) => prefix(proc.name, chunk)); + + child.on('exit', (code, signal) => { + if (shuttingDown) { + return; + } + stopAll(); + if (signal) { + process.exit(1); + } + process.exit(code ?? 0); + }); + + child.on('error', (error) => { + process.stderr.write(`[web:${proc.name}] ${error.message}\n`); + stopAll(); + process.exit(1); + }); +} + +process.on('SIGINT', () => stopAllAndExit('SIGINT', 130)); +process.on('SIGTERM', () => stopAllAndExit('SIGTERM', 143)); diff --git a/scripts/smoke-package-bundles.js b/scripts/smoke-package-bundles.js index a7dac22a..db27cf40 100644 --- a/scripts/smoke-package-bundles.js +++ b/scripts/smoke-package-bundles.js @@ -22,6 +22,7 @@ const bundles = [ ], bin: 'dotcontext', requiresPrompts: true, + requiresWebUiDist: true, }, { slug: 'harness', @@ -148,6 +149,13 @@ function smokeBundle(bundle) { assert(fs.existsSync(path.join(bundleRoot, 'prompts')), `${bundle.slug}: prompts missing`); } + if (bundle.requiresWebUiDist) { + assert( + fs.existsSync(path.join(bundleRoot, 'web-ui', 'dist', 'index.html')), + `${bundle.slug}: built web UI missing` + ); + } + const mod = requireFresh(mainPath); for (const exportName of bundle.expectedExports) { assert(mod[exportName], `${bundle.slug}: missing export ${exportName}`); @@ -156,6 +164,7 @@ function smokeBundle(bundle) { if (bundle.slug === 'cli') { const helpOutput = runBundleCommand(bundleRoot, ['@dotcontext/cli', '--help']); assert(helpOutput.includes('dotcontext'), 'cli: local npm exec help failed'); + assert(/\n\s+web\b/.test(helpOutput), 'cli: dotcontext web command missing from help'); } if (bundle.slug === 'mcp') { diff --git a/src/__tests__/cli.test.ts b/src/__tests__/cli.test.ts index 2a9f183b..fcf95ced 100644 --- a/src/__tests__/cli.test.ts +++ b/src/__tests__/cli.test.ts @@ -18,6 +18,7 @@ describe('CLI Commands', () => { expect(output).toContain('reverse-sync'); expect(output).toContain('mcp:install'); expect(output).toContain('mcp:uninstall'); + expect(output).toMatch(/\n\s+web\b/); expect(output).toContain('admin'); expect(output).not.toContain('report'); expect(output).not.toContain('sync-agents'); @@ -38,6 +39,14 @@ describe('CLI Commands', () => { expect(output).toContain('--hook-format'); }); + it('should expose web dashboard development options', () => { + const output = execSync(`node ${cliPath} web --help`, { encoding: 'utf8' }); + expect(output).toContain('--api-only'); + expect(output).toContain('--no-open'); + expect(output).toContain('--port'); + expect(output).toContain('--host'); + }); + it('should display version when --version flag is used', () => { const output = execSync(`node ${cliPath} --version`, { encoding: 'utf8' }); expect(output).toMatch(/\d+\.\d+\.\d+/); diff --git a/src/bin/dotcontext.ts b/src/bin/dotcontext.ts index 57b73cf0..848fb17b 100644 --- a/src/bin/dotcontext.ts +++ b/src/bin/dotcontext.ts @@ -2,6 +2,8 @@ import { Command } from 'commander'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import { exec } from 'child_process'; import { colors, typography } from '../utils/theme'; import { @@ -49,6 +51,12 @@ import { import { startMCPServer, } from '../mcp'; +import { + startWebServer, + resolveWebUiDistDir, + DEFAULT_WEB_HOST, + DEFAULT_WEB_PORT, +} from '../web'; import { WorkflowService, HarnessWorkflowActionService, @@ -285,6 +293,88 @@ program } }); +interface WebCommandOptions { + port: string; + host: string; + open: boolean; + apiOnly?: boolean; +} + +/** + * Best-effort open of the default browser. Never fatal: headless/CI + * environments (no display, no `xdg-open`/`open`/`start`) simply skip it. + */ +function openInBrowser(url: string): void { + const platform = process.platform; + const command = + platform === 'darwin' ? `open "${url}"` + : platform === 'win32' ? `start "" "${url}"` + : `xdg-open "${url}"`; + + exec(command, (error) => { + if (error) { + process.stderr.write(`[web] Could not open browser automatically: ${error.message}\n`); + } + }); +} + +program + .command('web') + .description('Start the dotcontext web dashboard (REST + SSE API and the built web UI)') + .option('-p, --port ', 'Port to listen on', String(DEFAULT_WEB_PORT)) + .option('--host ', `Host to bind to (default: ${DEFAULT_WEB_HOST}; binding elsewhere has no auth)`, DEFAULT_WEB_HOST) + .option('--api-only', 'Start only the REST + SSE API for web-ui development') + .option('--no-open', 'Do not open the dashboard in a browser automatically') + .action(async (options: WebCommandOptions) => { + const repoPath = process.cwd(); + const distDir = resolveWebUiDistDir(); + + if (!options.apiOnly && !(await fs.pathExists(distDir))) { + ui.displayError( + 'Web UI is not built yet.', + new Error(`Expected to find ${distDir}. Run "npm run build:web-ui" first, then retry "dotcontext web".`) + ); + process.exit(1); + return; + } + + const port = Number(options.port); + if (!Number.isInteger(port) || port <= 0) { + ui.displayError('Invalid --port value.', new Error(`"${options.port}" is not a valid port number.`)); + process.exit(1); + return; + } + + try { + const handle = await startWebServer({ + repoPath, + port, + host: options.host, + }); + + if (options.apiOnly) { + ui.displaySuccess(`dotcontext web API running at ${handle.url}`); + ui.displayInfo('Web UI dev server', 'Run "npm run dev:web-ui" and open the Vite URL.'); + } else { + ui.displaySuccess(`dotcontext web dashboard running at ${handle.url}`); + } + + if (options.open && !options.apiOnly) { + openInBrowser(handle.url); + } + + registerProcessShutdown(handle, { + onError: (error) => { + process.stderr.write(`[web] Shutdown error: ${error}\n`); + }, + exit: (code) => process.exit(code), + }); + } catch (error) { + ui.displayError('Failed to start the web dashboard.', error as Error); + process.exit(1); + } + }); + class McpHookOptionError extends Error {} interface McpInstallCommandOptions { diff --git a/src/harness/application/agents/agentsService.ts b/src/harness/application/agents/agentsService.ts index 438c1ef0..88e05b46 100644 --- a/src/harness/application/agents/agentsService.ts +++ b/src/harness/application/agents/agentsService.ts @@ -47,11 +47,15 @@ export class HarnessAgentsService { async getInfo(agentType: string): Promise> { const linker = createPlanLinker(this.repoPath); - const info = await linker.getAgentInfo(agentType); + const [info, content] = await Promise.all([ + linker.getAgentInfo(agentType), + linker.getAgentContent(agentType), + ]); return { success: true, agent: info, + content: content ?? undefined, }; } diff --git a/src/harness/application/docs/HarnessDocsService.ts b/src/harness/application/docs/HarnessDocsService.ts new file mode 100644 index 00000000..cf6657e8 --- /dev/null +++ b/src/harness/application/docs/HarnessDocsService.ts @@ -0,0 +1,155 @@ +/** + * Harness Docs Service + * + * Transport-agnostic listing and content retrieval for `.context/docs/*.md`. + * Mirrors the shape of `HarnessSkillsService.list()` / `getContent()` so the + * web dashboard (and any future adapter) can browse generated/filled docs the + * same way it browses skills. No equivalent listing service exists for docs + * today, so this is a small, intentionally narrow addition (Phase 3 of the + * web-interface plan). + */ + +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { parseFrontMatter, parseScaffoldFrontMatter } from '../../../utils/frontMatter'; +import { PathValidator } from '../../../utils/pathSecurity'; + +export interface HarnessDocsServiceOptions { + repoPath: string; +} + +export interface HarnessDocEntry { + name: string; + title: string; + description?: string; + category?: string; + status: 'filled' | 'unfilled'; +} + +export interface HarnessDocContent { + name: string; + frontMatter: Record; + content: string; +} + +const DOCS_RELATIVE_DIR = path.join('.context', 'docs'); +const MARKDOWN_EXTENSION = '.md'; + +function deriveTitle(body: string, fallback: string): string { + const match = body.match(/^#\s+(.+?)\s*$/m); + return match ? match[1].trim() : fallback; +} + +function humanizeName(name: string): string { + return name + .split(/[-_]+/g) + .filter(Boolean) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(' ') || name; +} + +/** + * Resolve frontmatter (v2 scaffold format, falling back to legacy v1) plus + * the markdown body with the frontmatter block stripped. + */ +function readDocFrontMatter(content: string): { + frontMatter: Record | null; + body: string; +} { + const scaffold = parseScaffoldFrontMatter(content); + if (scaffold.frontMatter) { + // ParsedScaffoldFrontmatter has concrete fields (no index signature); this + // service only ever forwards frontmatter as a generic JSON-serializable + // bag (see HarnessDocContent), so the widen-to-Record cast is safe here. + return { frontMatter: scaffold.frontMatter as unknown as Record, body: scaffold.body }; + } + + const legacy = parseFrontMatter(content); + return { frontMatter: legacy.frontMatter, body: legacy.body }; +} + +export class HarnessDocsService { + constructor(private readonly options: HarnessDocsServiceOptions) {} + + private get repoPath(): string { + return this.options.repoPath || process.cwd(); + } + + private get docsDir(): string { + return path.join(this.repoPath, DOCS_RELATIVE_DIR); + } + + /** + * List docs directly under `.context/docs/*.md`, mirroring + * `HarnessSkillsService.list()`'s flat summary shape. + */ + async list(): Promise { + if (!(await fs.pathExists(this.docsDir))) { + return []; + } + + const entries = await fs.readdir(this.docsDir, { withFileTypes: true }); + const fileNames = entries + .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(MARKDOWN_EXTENSION)) + .map((entry) => entry.name) + .sort((a, b) => a.localeCompare(b)); + + const docs: HarnessDocEntry[] = []; + + for (const fileName of fileNames) { + const name = fileName.slice(0, -MARKDOWN_EXTENSION.length); + + let raw: string; + try { + raw = await fs.readFile(path.join(this.docsDir, fileName), 'utf-8'); + } catch { + continue; + } + + const { frontMatter, body } = readDocFrontMatter(raw); + const description = typeof frontMatter?.description === 'string' && frontMatter.description.length > 0 + ? frontMatter.description + : undefined; + const category = typeof frontMatter?.category === 'string' && frontMatter.category.length > 0 + ? frontMatter.category + : undefined; + const titleFallback = typeof frontMatter?.name === 'string' && frontMatter.name.length > 0 + ? frontMatter.name + : humanizeName(name); + + docs.push({ + name, + title: deriveTitle(body, titleFallback), + description, + category, + status: frontMatter?.status === 'unfilled' ? 'unfilled' : 'filled', + }); + } + + return docs; + } + + /** + * Get the parsed frontmatter and body content for a single doc by name + * (file name without the `.md` extension). Resolves the requested doc + * through `PathValidator` so traversal-style names can never escape + * `.context/docs`. + */ + async getContent(name: string): Promise { + const validator = new PathValidator(this.docsDir); + const filePath = validator.validatePath(`${name}${MARKDOWN_EXTENSION}`); + + if (!(await fs.pathExists(filePath))) { + throw new Error(`Doc not found: ${name}`); + } + + const raw = await fs.readFile(filePath, 'utf-8'); + const { frontMatter, body } = readDocFrontMatter(raw); + + return { + name, + frontMatter: frontMatter ?? {}, + content: body, + }; + } +} diff --git a/src/harness/application/docs/__tests__/HarnessDocsService.test.ts b/src/harness/application/docs/__tests__/HarnessDocsService.test.ts new file mode 100644 index 00000000..e622ad9b --- /dev/null +++ b/src/harness/application/docs/__tests__/HarnessDocsService.test.ts @@ -0,0 +1,112 @@ +import * as fs from 'fs-extra'; +import * as os from 'os'; +import * as path from 'path'; + +import { HarnessDocsService } from '../HarnessDocsService'; + +describe('HarnessDocsService', () => { + let tempDir: string; + let service: HarnessDocsService; + + beforeEach(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-docs-')); + service = new HarnessDocsService({ repoPath: tempDir }); + }); + + afterEach(async () => { + await fs.remove(tempDir); + }); + + it('returns an empty list when .context/docs does not exist', async () => { + expect(await service.list()).toEqual([]); + }); + + it('lists docs using v2 scaffold frontmatter, sorted by file name', async () => { + const docsDir = path.join(tempDir, '.context', 'docs'); + await fs.ensureDir(docsDir); + + await fs.writeFile( + path.join(docsDir, 'zeta.md'), + [ + '---', + 'type: docs', + 'name: zeta', + 'description: Zeta doc', + 'category: reference', + 'status: filled', + 'generated: "2026-06-30"', + 'scaffoldVersion: "2.0.0"', + '---', + '', + '# Zeta Doc', + '', + 'Body content.', + '', + ].join('\n') + ); + + await fs.writeFile( + path.join(docsDir, 'alpha.md'), + [ + '---', + 'type: docs', + 'name: alpha', + 'status: unfilled', + 'generated: "2026-06-30"', + 'scaffoldVersion: "2.0.0"', + '---', + '', + '# Alpha Doc', + '', + ].join('\n') + ); + + const docs = await service.list(); + + expect(docs.map((doc) => doc.name)).toEqual(['alpha', 'zeta']); + expect(docs[0]).toMatchObject({ name: 'alpha', title: 'Alpha Doc', status: 'unfilled' }); + expect(docs[1]).toMatchObject({ + name: 'zeta', + title: 'Zeta Doc', + description: 'Zeta doc', + category: 'reference', + status: 'filled', + }); + }); + + it('falls back to a humanized file name when no title/frontmatter name is present', async () => { + const docsDir = path.join(tempDir, '.context', 'docs'); + await fs.ensureDir(docsDir); + await fs.writeFile(path.join(docsDir, 'getting-started.md'), 'No frontmatter, no heading.'); + + const docs = await service.list(); + + expect(docs).toEqual([ + expect.objectContaining({ name: 'getting-started', title: 'Getting Started', status: 'filled' }), + ]); + }); + + it('getContent returns parsed frontmatter and body with the frontmatter block stripped', async () => { + const docsDir = path.join(tempDir, '.context', 'docs'); + await fs.ensureDir(docsDir); + await fs.writeFile( + path.join(docsDir, 'tooling.md'), + ['---', 'type: docs', 'name: tooling', 'status: filled', 'generated: "2026-06-30"', 'scaffoldVersion: "2.0.0"', '---', '', '# Tooling', '', 'Details here.', ''].join('\n') + ); + + const doc = await service.getContent('tooling'); + + expect(doc.name).toBe('tooling'); + expect(doc.frontMatter).toMatchObject({ name: 'tooling', status: 'filled' }); + expect(doc.content).toContain('# Tooling'); + expect(doc.content).not.toContain('scaffoldVersion'); + }); + + it('getContent throws for a missing doc', async () => { + await expect(service.getContent('missing')).rejects.toThrow('Doc not found: missing'); + }); + + it('getContent rejects path traversal attempts via PathValidator', async () => { + await expect(service.getContent('../../etc/passwd')).rejects.toThrow(); + }); +}); diff --git a/src/harness/application/docs/index.ts b/src/harness/application/docs/index.ts new file mode 100644 index 00000000..be0867a5 --- /dev/null +++ b/src/harness/application/docs/index.ts @@ -0,0 +1 @@ +export * from './HarnessDocsService'; diff --git a/src/harness/application/index.ts b/src/harness/application/index.ts index 721c0d0f..8ace466c 100644 --- a/src/harness/application/index.ts +++ b/src/harness/application/index.ts @@ -4,6 +4,7 @@ export * from './workflow'; export * from './context'; export * from './agents'; export * from './skills'; +export * from './docs'; export * from './sensors'; export * from './contracts'; export * from './execution'; @@ -21,3 +22,4 @@ export * as context from './context'; export * as exchange from './exchange'; export * as agents from './agents'; export * as skills from './skills'; +export * as docs from './docs'; diff --git a/src/harness/application/workflow/index.ts b/src/harness/application/workflow/index.ts index 0f1aa163..0e0fed58 100644 --- a/src/harness/application/workflow/index.ts +++ b/src/harness/application/workflow/index.ts @@ -10,3 +10,14 @@ export * from './derivedPlanTaskContractBuilder'; export * from './fileCollaborationStore'; export * from './harnessSessionFacade'; export * from './plansService'; +export { + WorkflowGuideService, + type WorkflowGuideServiceOptions, +} from './workflowGuideService'; +export type { + WorkflowGuideIntent, + WorkflowGuideFormat, + WorkflowGuideSkillRef, + WorkflowGuideDecision, + WorkflowGuideResult, +} from './workflowGuideTypes'; diff --git a/src/harness/domain/workflow/plans/planLinker.ts b/src/harness/domain/workflow/plans/planLinker.ts index cf6036d8..d7d969f1 100644 --- a/src/harness/domain/workflow/plans/planLinker.ts +++ b/src/harness/domain/workflow/plans/planLinker.ts @@ -98,6 +98,10 @@ export class PlanLinker { return this.agentRegistry.getAgentMetadata(agentType); } + async getAgentContent(agentType: string): Promise { + return this.agentRegistry.getPlaybookContent(agentType); + } + // --------------------------------------------------------------------------- // Initial linking + queries // --------------------------------------------------------------------------- diff --git a/src/harness/index.ts b/src/harness/index.ts index fa1b6cd9..f4183a74 100644 --- a/src/harness/index.ts +++ b/src/harness/index.ts @@ -108,6 +108,10 @@ export { HarnessSkillsService, type HarnessSkillsServiceOptions, type HarnessBootstrapStatusResult, + HarnessDocsService, + type HarnessDocsServiceOptions, + type HarnessDocEntry, + type HarnessDocContent, HarnessRuntimeStateService, type HarnessRuntimeStateServiceOptions, type HarnessSessionStatus, @@ -181,6 +185,15 @@ export { HarnessPlansService, type HarnessPlansServiceOptions, } from './application/workflow/plansService'; +export { + WorkflowGuideService, + type WorkflowGuideServiceOptions, + type WorkflowGuideIntent, + type WorkflowGuideFormat, + type WorkflowGuideSkillRef, + type WorkflowGuideDecision, + type WorkflowGuideResult, +} from './application/workflow'; export { getScaleName, diff --git a/src/tests/architecture/__tests__/boundaries.test.ts b/src/tests/architecture/__tests__/boundaries.test.ts index b6d5cb66..006c4ed8 100644 --- a/src/tests/architecture/__tests__/boundaries.test.ts +++ b/src/tests/architecture/__tests__/boundaries.test.ts @@ -3,6 +3,7 @@ import * as path from 'path'; const SRC_ROOT = path.resolve(__dirname, '..', '..', '..'); const INTEGRATIONS_ROOT = path.join(SRC_ROOT, 'integrations'); +const WEB_ROOT = path.join(SRC_ROOT, 'web'); const HARNESS_DOMAIN_ROOT = path.join(SRC_ROOT, 'harness', 'domain'); const HARNESS_APPLICATION_ROOT = path.join(SRC_ROOT, 'harness', 'application'); const HARNESS_ADAPTERS_ROOT = path.join(SRC_ROOT, 'harness', 'adapters'); @@ -212,6 +213,27 @@ describe('architecture boundaries', () => { expect(violations).toEqual([]); }); + it('keeps the web adapter independent from cli and mcp surfaces', () => { + // `src/web` is a fifth boundary alongside cli/harness/mcp/integrations + // (see .context/docs/web-interface-architecture.md section 1): it must + // depend only on src/harness, exactly like src/mcp does, and never reach + // into src/cli or src/mcp. + const files = walk(WEB_ROOT); + expect(files.length).toBeGreaterThan(0); + + const violations = files + .flatMap(getImportReferences) + .filter(isForbiddenIntegrationImport); + + if (violations.length > 0) { + throw new Error( + `src/web must not import cli or mcp surfaces.\n\n${formatViolations(violations)}` + ); + } + + expect(violations).toEqual([]); + }); + it('keeps the harness runtime independent from cli and mcp surfaces', () => { // The asymmetric boundary `cli -> harness <- mcp` requires that harness // depend on neither adapter. cli/mcp are adapters over the harness; the diff --git a/src/web/__tests__/router.test.ts b/src/web/__tests__/router.test.ts new file mode 100644 index 00000000..55591f92 --- /dev/null +++ b/src/web/__tests__/router.test.ts @@ -0,0 +1,106 @@ +import { matchRoute, createRouter, type RouteContext } from '../router'; +import type { RuntimeWatcher } from '../events/runtimeWatcher'; + +function buildContext(): RouteContext { + return { + repoPath: '/tmp/does-not-matter', + runtimeWatcher: { on: jest.fn(), off: jest.fn() } as unknown as RuntimeWatcher, + }; +} + +describe('matchRoute', () => { + it('matches a static route with no params', () => { + const routes = [ + { method: 'GET', segments: ['api', 'docs'], handler: jest.fn() }, + ]; + + const match = matchRoute(routes, 'GET', '/api/docs'); + + expect(match).not.toBeNull(); + expect(match?.params).toEqual({}); + }); + + it('extracts dynamic :param segments', () => { + const routes = [ + { method: 'GET', segments: ['api', 'docs', ':name'], handler: jest.fn() }, + ]; + + const match = matchRoute(routes, 'GET', '/api/docs/project-overview'); + + expect(match).not.toBeNull(); + expect(match?.params).toEqual({ name: 'project-overview' }); + }); + + it('decodes URI-encoded param segments', () => { + const routes = [ + { method: 'GET', segments: ['api', 'sessions', ':id'], handler: jest.fn() }, + ]; + + const match = matchRoute(routes, 'GET', '/api/sessions/abc%2Fdef'); + + expect(match?.params).toEqual({ id: 'abc/def' }); + }); + + it('does not match a different HTTP method', () => { + const routes = [ + { method: 'GET', segments: ['api', 'docs'], handler: jest.fn() }, + ]; + + expect(matchRoute(routes, 'POST', '/api/docs')).toBeNull(); + }); + + it('does not match a different segment count', () => { + const routes = [ + { method: 'GET', segments: ['api', 'docs', ':name'], handler: jest.fn() }, + ]; + + expect(matchRoute(routes, 'GET', '/api/docs')).toBeNull(); + expect(matchRoute(routes, 'GET', '/api/docs/a/b')).toBeNull(); + }); + + it('does not match when a static segment differs', () => { + const routes = [ + { method: 'GET', segments: ['api', 'docs', ':name'], handler: jest.fn() }, + ]; + + expect(matchRoute(routes, 'GET', '/api/skills/foo')).toBeNull(); + }); +}); + +describe('createRouter', () => { + it('returns false (unmatched) for an unknown /api/* path without throwing', async () => { + const dispatch = createRouter(buildContext()); + const req = { method: 'GET' } as unknown as Parameters[0]; + const res = { headersSent: false, writeHead: jest.fn(), end: jest.fn() } as unknown as Parameters[1]; + + const matched = await dispatch(req, res, '/api/totally-unknown'); + + expect(matched).toBe(false); + }); + + it('catches handler errors and responds with a 500 envelope instead of throwing', async () => { + const dispatch = createRouter(buildContext()); + const req = { method: 'GET' } as unknown as Parameters[0]; + + let writtenStatus: number | undefined; + let writtenBody = ''; + const res = { + headersSent: false, + writeHead: (status: number) => { + writtenStatus = status; + }, + end: (body?: string) => { + writtenBody = body ?? ''; + }, + } as unknown as Parameters[1]; + + // /api/docs/:name resolves to a real handler (getDoc), which will reject + // because the repo path does not exist — exercising the router's + // try/catch around handler execution. + const matched = await dispatch(req, res, '/api/docs/does-not-exist'); + + expect(matched).toBe(true); + expect(writtenStatus).toBe(404); + expect(JSON.parse(writtenBody)).toHaveProperty('error.message'); + }); +}); diff --git a/src/web/__tests__/server.test.ts b/src/web/__tests__/server.test.ts new file mode 100644 index 00000000..ea540cb1 --- /dev/null +++ b/src/web/__tests__/server.test.ts @@ -0,0 +1,257 @@ +import * as fs from 'fs-extra'; +import * as os from 'os'; +import * as path from 'path'; +import * as http from 'http'; + +import { startWebServer, type WebServerHandle } from '../server'; +import { HarnessRuntimeStateService } from '../../harness'; + +interface JsonResponse { + status: number; + body: T; +} + +function getJson(url: string): Promise> { + return new Promise((resolve, reject) => { + http + .get(url, (res) => { + const chunks: Buffer[] = []; + res.on('data', (chunk) => chunks.push(chunk)); + res.on('end', () => { + const raw = Buffer.concat(chunks).toString('utf-8'); + try { + resolve({ status: res.statusCode ?? 0, body: raw ? JSON.parse(raw) : undefined }); + } catch (error) { + reject(error); + } + }); + }) + .on('error', reject); + }); +} + +describe('startWebServer', () => { + let tempDir: string; + let handle: WebServerHandle; + + beforeAll(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'web-server-')); + + await fs.ensureDir(path.join(tempDir, '.context', 'docs')); + await fs.writeFile( + path.join(tempDir, '.context', 'docs', 'project-overview.md'), + '---\ntype: docs\nname: project-overview\ndescription: Overview doc\nstatus: filled\ngenerated: "2026-06-30"\nscaffoldVersion: "2.0.0"\n---\n\n# Project Overview\n\nHello world.\n' + ); + + // Binds an ephemeral port (`port: 0`) so parallel test runs never collide. + handle = await startWebServer({ repoPath: tempDir, port: 0 }); + }); + + afterAll(async () => { + await handle.stop(); + await fs.remove(tempDir); + }); + + it('binds to localhost by default', () => { + expect(handle.host).toBe('127.0.0.1'); + expect(handle.url).toBe(`http://127.0.0.1:${handle.port}`); + }); + + it('GET /api/docs lists docs discovered under .context/docs', async () => { + const response = await getJson<{ data: Array<{ name: string; title: string }> }>( + `${handle.url}/api/docs` + ); + + expect(response.status).toBe(200); + expect(response.body.data).toEqual([ + expect.objectContaining({ name: 'project-overview', title: 'Project Overview' }), + ]); + }); + + it('GET /api/docs/:name returns the doc content envelope', async () => { + const response = await getJson<{ data: { name: string; content: string } }>( + `${handle.url}/api/docs/project-overview` + ); + + expect(response.status).toBe(200); + expect(response.body.data.name).toBe('project-overview'); + expect(response.body.data.content).toContain('Hello world.'); + }); + + it('GET /api/docs/:name returns a 404 error envelope for a missing doc', async () => { + const response = await getJson<{ error: { message: string } }>( + `${handle.url}/api/docs/does-not-exist` + ); + + expect(response.status).toBe(404); + expect(response.body.error.message).toMatch(/not found/i); + }); + + it('GET /api/sessions reflects sessions written via the harness runtime state service', async () => { + const runtimeState = new HarnessRuntimeStateService({ repoPath: tempDir }); + const session = await runtimeState.createSession({ name: 'web-route-test' }); + + const response = await getJson<{ data: Array<{ id: string }> }>(`${handle.url}/api/sessions`); + + expect(response.status).toBe(200); + expect(response.body.data.map((entry) => entry.id)).toContain(session.id); + }); + + it('GET /api/workflow/status reports no workflow when none has been initialized', async () => { + const response = await getJson<{ data: { status: unknown; summary: unknown } }>( + `${handle.url}/api/workflow/status` + ); + + expect(response.status).toBe(200); + expect(response.body.data).toEqual({ status: null, summary: null }); + }); + + it('GET /api/workflow/guide returns a guide payload', async () => { + const response = await getJson<{ data: unknown }>(`${handle.url}/api/workflow/guide`); + + expect(response.status).toBe(200); + expect(response.body.data).toBeTruthy(); + }); + + it('GET /api/workflow/plans lists linked plans (empty when none are linked)', async () => { + const response = await getJson<{ data: { success: boolean; plans: { active: unknown[] } } }>( + `${handle.url}/api/workflow/plans` + ); + + expect(response.status).toBe(200); + expect(response.body.data.success).toBe(true); + expect(Array.isArray(response.body.data.plans.active)).toBe(true); + }); + + it('GET /api/workflow/plans/:slug returns a not-found-style result for an unknown plan', async () => { + const response = await getJson<{ data: { success?: boolean } }>( + `${handle.url}/api/workflow/plans/does-not-exist` + ); + + expect(response.status).toBe(200); + expect(response.body.data).toBeTruthy(); + }); + + it('GET /api/skills lists skills (empty when .context/skills does not exist)', async () => { + const response = await getJson<{ data: { skills?: unknown[] } }>(`${handle.url}/api/skills`); + + expect(response.status).toBe(200); + expect(response.body.data).toBeTruthy(); + }); + + it('GET /api/agents discovers the built-in agent roster', async () => { + const response = await getJson<{ data: { totalAgents: number; agents: { builtIn: string[] } } }>( + `${handle.url}/api/agents` + ); + + expect(response.status).toBe(200); + expect(response.body.data.totalAgents).toBeGreaterThan(0); + expect(response.body.data.agents.builtIn).toContain('architect-specialist'); + }); + + it('GET /api/agents/:type returns info + docs for a known built-in agent', async () => { + const response = await getJson<{ data: { info: unknown; docs: unknown } }>( + `${handle.url}/api/agents/architect-specialist` + ); + + expect(response.status).toBe(200); + expect(response.body.data.info).toBeTruthy(); + expect(response.body.data.docs).toBeTruthy(); + }); + + it('GET /api/sessions/:id/traces, /artifacts, /checkpoints reflect a real session', async () => { + const runtimeState = new HarnessRuntimeStateService({ repoPath: tempDir }); + const session = await runtimeState.createSession({ name: 'web-route-detail-test' }); + await runtimeState.appendTrace(session.id, { level: 'info', event: 'unit.test', message: 'hi' }); + await runtimeState.addArtifact(session.id, { name: 'note', kind: 'text', content: 'hello' }); + await runtimeState.checkpointSession(session.id, { note: 'checkpoint-1' }); + + const [traces, artifacts, checkpoints] = await Promise.all([ + getJson<{ data: Array<{ event: string }> }>(`${handle.url}/api/sessions/${session.id}/traces`), + getJson<{ data: Array<{ name: string }> }>(`${handle.url}/api/sessions/${session.id}/artifacts`), + getJson<{ data: Array<{ note?: string }> }>(`${handle.url}/api/sessions/${session.id}/checkpoints`), + ]); + + expect(traces.status).toBe(200); + expect(traces.body.data.map((t) => t.event)).toContain('unit.test'); + + expect(artifacts.status).toBe(200); + expect(artifacts.body.data.map((a) => a.name)).toContain('note'); + + expect(checkpoints.status).toBe(200); + expect(checkpoints.body.data.some((c) => c.note === 'checkpoint-1')).toBe(true); + }); + + it('GET /api/sessions/:id/traces returns an empty list for an unknown session rather than throwing', async () => { + const response = await getJson<{ data: unknown[] }>(`${handle.url}/api/sessions/does-not-exist/traces`); + + expect(response.status).toBe(200); + expect(response.body.data).toEqual([]); + }); + + it('GET /api/sessions/:id returns a 404 error envelope for an unknown session', async () => { + const response = await getJson<{ error: { message: string } }>(`${handle.url}/api/sessions/does-not-exist`); + + expect(response.status).toBe(404); + expect(response.body.error).toBeTruthy(); + }); + + it('returns 404 for an unmatched /api/* path instead of falling through to static serving', async () => { + const response = await getJson(`${handle.url}/api/nope`); + expect(response.status).toBe(404); + }); + + it('serves the built SPA index.html for the root path', async () => { + const html = await new Promise<{ status: number; body: string }>((resolve, reject) => { + http + .get(handle.url, (res) => { + const chunks: Buffer[] = []; + res.on('data', (chunk) => chunks.push(chunk)); + res.on('end', () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString('utf-8') })); + }) + .on('error', reject); + }); + + expect(html.status).toBe(200); + expect(html.body.toLowerCase()).toContain(' { + const html = await new Promise<{ status: number; body: string }>((resolve, reject) => { + http + .get(`${handle.url}/workflow`, (res) => { + const chunks: Buffer[] = []; + res.on('data', (chunk) => chunks.push(chunk)); + res.on('end', () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString('utf-8') })); + }) + .on('error', reject); + }); + + expect(html.status).toBe(200); + expect(html.body.toLowerCase()).toContain(' { + const event = await new Promise<{ event: string; data: string }>((resolve, reject) => { + const req = http.get(`${handle.url}/api/events`, (res) => { + let buffer = ''; + res.on('data', (chunk) => { + buffer += chunk.toString('utf-8'); + const match = buffer.match(/event: (\w+)\ndata: (.+)\n\n/); + if (match) { + req.destroy(); + resolve({ event: match[1], data: match[2] }); + } + }); + res.on('error', reject); + }); + req.on('error', () => { + // Destroying the request after resolving triggers a benign socket + // error on some Node versions; only reject if we never resolved. + }); + }); + + expect(event.event).toBe('hello'); + expect(() => JSON.parse(event.data)).not.toThrow(); + }); +}); diff --git a/src/web/events/__tests__/runtimeWatcher.test.ts b/src/web/events/__tests__/runtimeWatcher.test.ts new file mode 100644 index 00000000..2ef5e74e --- /dev/null +++ b/src/web/events/__tests__/runtimeWatcher.test.ts @@ -0,0 +1,69 @@ +import * as fs from 'fs-extra'; +import * as os from 'os'; +import * as path from 'path'; + +import { createRuntimeWatcher, type RuntimeWatcher, type RuntimeChangeEvent } from '../runtimeWatcher'; + +describe('RuntimeWatcher', () => { + let tempDir: string; + let watcher: RuntimeWatcher; + + beforeEach(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-watcher-')); + await fs.ensureDir(path.join(tempDir, '.context', 'runtime')); + }); + + afterEach(async () => { + await watcher?.close(); + await fs.remove(tempDir); + }); + + it('emits a debounced runtime-change event with repo-relative POSIX paths', async () => { + watcher = createRuntimeWatcher({ repoPath: tempDir, debounceMs: 50 }); + await watcher.ready(); + + const eventPromise = new Promise((resolve) => { + watcher.once('runtime-change', resolve); + }); + + await fs.writeFile( + path.join(tempDir, '.context', 'runtime', 'sessions.json'), + JSON.stringify({ sessions: [] }) + ); + + const event = await eventPromise; + + expect(event.paths).toEqual(['.context/runtime/sessions.json']); + }); + + it('coalesces multiple rapid writes into a single batched event', async () => { + watcher = createRuntimeWatcher({ repoPath: tempDir, debounceMs: 80 }); + await watcher.ready(); + + const events: RuntimeChangeEvent[] = []; + watcher.on('runtime-change', (event: RuntimeChangeEvent) => events.push(event)); + + await fs.writeFile(path.join(tempDir, '.context', 'runtime', 'a.json'), '{}'); + await fs.writeFile(path.join(tempDir, '.context', 'runtime', 'b.json'), '{}'); + + // Wait comfortably past the debounce window so both writes land in one batch. + await new Promise((resolve) => setTimeout(resolve, 250)); + + expect(events).toHaveLength(1); + expect(events[0].paths.sort()).toEqual(['.context/runtime/a.json', '.context/runtime/b.json']); + }); + + it('stops emitting events after close()', async () => { + watcher = createRuntimeWatcher({ repoPath: tempDir, debounceMs: 30 }); + await watcher.ready(); + + const onChange = jest.fn(); + watcher.on('runtime-change', onChange); + + await watcher.close(); + await fs.writeFile(path.join(tempDir, '.context', 'runtime', 'after-close.json'), '{}'); + await new Promise((resolve) => setTimeout(resolve, 150)); + + expect(onChange).not.toHaveBeenCalled(); + }); +}); diff --git a/src/web/events/runtimeWatcher.ts b/src/web/events/runtimeWatcher.ts new file mode 100644 index 00000000..936bbff2 --- /dev/null +++ b/src/web/events/runtimeWatcher.ts @@ -0,0 +1,113 @@ +/** + * Runtime Watcher + * + * Watches `.context/runtime/**` (sessions, workflows, contracts, evaluations) + * for changes and emits a single debounced `runtime-change` event per batch + * of writes, per `.context/docs/web-interface-architecture.md` section 4.6. + * + * Chokidar already debounces *per-file* stability via `awaitWriteFinish`, but + * a session checkpoint can touch several files (session.json, traces.jsonl, + * artifact files) in quick succession; this module additionally coalesces + * everything chokidar reports inside one `debounceMs` window into a single + * `{ paths: string[] }` event so SSE clients see one event per logical + * batch of runtime writes, not one per touched file. + * + * One instance is created per `src/web` server process (see `server.ts`) and + * shared across every connected SSE client (`routes/events.ts`). + */ + +import { EventEmitter } from 'events'; +import * as path from 'path'; +import { watch, type FSWatcher } from 'chokidar'; + +export interface RuntimeChangeEvent { + /** Repo-relative paths (POSIX separators) that changed in this batch. */ + paths: string[]; +} + +export interface RuntimeWatcherOptions { + repoPath: string; + /** Debounce window for batching/coalescing changes. Defaults to ~300ms. */ + debounceMs?: number; +} + +const DEFAULT_DEBOUNCE_MS = 300; +const RUNTIME_RELATIVE_DIR = path.join('.context', 'runtime'); + +/** + * Emits `runtime-change` ({ paths: string[] }) and `error` events. + */ +export class RuntimeWatcher extends EventEmitter { + private readonly watcher: FSWatcher; + private readonly repoPath: string; + private readonly debounceMs: number; + private readonly pendingPaths = new Set(); + private flushTimer: ReturnType | null = null; + private closed = false; + + constructor(options: RuntimeWatcherOptions) { + super(); + this.repoPath = path.resolve(options.repoPath); + this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS; + + const watchDir = path.join(this.repoPath, RUNTIME_RELATIVE_DIR); + this.watcher = watch(watchDir, { + ignoreInitial: true, + awaitWriteFinish: { + stabilityThreshold: this.debounceMs, + pollInterval: Math.max(20, Math.floor(this.debounceMs / 6)), + }, + }); + + this.watcher.on('all', (_event, changedPath) => this.queueChange(changedPath)); + this.watcher.on('error', (error) => this.emit('error', error)); + } + + private toRepoRelative(changedPath: string): string { + const relative = path.relative(this.repoPath, changedPath); + return relative.split(path.sep).join('/'); + } + + private queueChange(changedPath: string): void { + if (this.closed) { + return; + } + + this.pendingPaths.add(this.toRepoRelative(changedPath)); + + if (this.flushTimer) { + clearTimeout(this.flushTimer); + } + this.flushTimer = setTimeout(() => this.flush(), this.debounceMs); + } + + private flush(): void { + this.flushTimer = null; + if (this.pendingPaths.size === 0) { + return; + } + + const paths = Array.from(this.pendingPaths).sort(); + this.pendingPaths.clear(); + const event: RuntimeChangeEvent = { paths }; + this.emit('runtime-change', event); + } + + /** Resolves once the initial filesystem scan has completed. */ + async ready(): Promise { + await new Promise((resolve) => this.watcher.once('ready', resolve)); + } + + async close(): Promise { + this.closed = true; + if (this.flushTimer) { + clearTimeout(this.flushTimer); + this.flushTimer = null; + } + await this.watcher.close(); + } +} + +export function createRuntimeWatcher(options: RuntimeWatcherOptions): RuntimeWatcher { + return new RuntimeWatcher(options); +} diff --git a/src/web/index.ts b/src/web/index.ts new file mode 100644 index 00000000..c2f72c4b --- /dev/null +++ b/src/web/index.ts @@ -0,0 +1,17 @@ +/** + * Web boundary exports. + * + * `src/web` is a fifth boundary alongside `cli`, `harness`, `mcp`, and + * `integrations` (see `.context/docs/web-interface-architecture.md` section + * 1): it depends only on `src/harness` application services, exactly like + * `src/mcp`, and never imports from `src/cli` or `src/mcp`. + */ + +export { + startWebServer, + resolveWebUiDistDir, + DEFAULT_WEB_HOST, + DEFAULT_WEB_PORT, + type StartWebServerOptions, + type WebServerHandle, +} from './server'; diff --git a/src/web/response.ts b/src/web/response.ts new file mode 100644 index 00000000..291e1bf6 --- /dev/null +++ b/src/web/response.ts @@ -0,0 +1,48 @@ +/** + * Web Response Helpers + * + * All `src/web` JSON routes share one envelope, per + * `.context/docs/web-interface-architecture.md` section 4: + * - success (2xx): `{ "data": }` + * - failure (4xx/5xx): `{ "error": { "message": string } }` + * + * This is intentionally a different shape from the ad hoc `{ success, ... }` + * objects returned by MCP-facing harness application services; routes pass + * those through unwrapped inside `data` (see route modules), they are never + * conflated with this HTTP-level envelope. + */ + +import type { ServerResponse } from 'http'; + +/** + * Extract a human-readable message from a thrown value, mirroring the + * `src/mcp/gateway/response.ts#createErrorResponse` convention. + */ +export function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +function writeJson(res: ServerResponse, status: number, body: unknown): void { + const json = JSON.stringify(body); + if (!res.headersSent) { + res.writeHead(status, { + 'Content-Type': 'application/json; charset=utf-8', + 'Content-Length': Buffer.byteLength(json), + }); + } + res.end(json); +} + +/** + * Send a successful `{ data }` envelope. + */ +export function sendData(res: ServerResponse, status: number, data: unknown): void { + writeJson(res, status, { data }); +} + +/** + * Send a failure `{ error: { message } }` envelope. + */ +export function sendError(res: ServerResponse, status: number, message: string): void { + writeJson(res, status, { error: { message } }); +} diff --git a/src/web/router.ts b/src/web/router.ts new file mode 100644 index 00000000..0b9ae5e7 --- /dev/null +++ b/src/web/router.ts @@ -0,0 +1,158 @@ +/** + * Web Router + * + * A small static `(method, pathname)` route table, mirroring (in spirit, not + * transport) the `src/mcp/gateway/*.ts` pattern of one handler module per + * resource area. No framework: routes are matched by splitting the pattern + * and the request pathname into segments and comparing them, extracting + * `:param` segments as we go. See + * `.context/docs/web-interface-architecture.md` section 2 (ADR-1). + */ + +import type { IncomingMessage, ServerResponse } from 'http'; + +import type { RuntimeWatcher } from './events/runtimeWatcher'; +import { sendError, errorMessage } from './response'; + +import * as docsRoutes from './routes/docs'; +import * as skillsRoutes from './routes/skills'; +import * as agentsRoutes from './routes/agents'; +import * as sessionsRoutes from './routes/sessions'; +import * as workflowRoutes from './routes/workflow'; +import * as eventsRoutes from './routes/events'; + +export interface RouteContext { + repoPath: string; + runtimeWatcher: RuntimeWatcher; +} + +export type RouteParams = Record; + +export type RouteHandler = ( + req: IncomingMessage, + res: ServerResponse, + params: RouteParams, + ctx: RouteContext +) => Promise | void; + +interface RouteDefinition { + method: string; + segments: string[]; + handler: RouteHandler; +} + +function splitPath(pathname: string): string[] { + return pathname.split('/').filter(Boolean); +} + +function route(method: string, pattern: string, handler: RouteHandler): RouteDefinition { + return { method, segments: splitPath(pattern), handler }; +} + +/** + * GET-only in this phase (read-only dashboard) — see section 4.7. + */ +function buildRoutes(): RouteDefinition[] { + return [ + route('GET', '/api/docs', docsRoutes.listDocs), + route('GET', '/api/docs/:name', docsRoutes.getDoc), + + route('GET', '/api/skills', skillsRoutes.listSkills), + route('GET', '/api/skills/:slug', skillsRoutes.getSkill), + + route('GET', '/api/agents', agentsRoutes.listAgents), + route('GET', '/api/agents/:type', agentsRoutes.getAgent), + + route('GET', '/api/sessions', sessionsRoutes.listSessions), + route('GET', '/api/sessions/:id', sessionsRoutes.getSession), + route('GET', '/api/sessions/:id/traces', sessionsRoutes.listTraces), + route('GET', '/api/sessions/:id/artifacts', sessionsRoutes.listArtifacts), + route('GET', '/api/sessions/:id/checkpoints', sessionsRoutes.listCheckpoints), + + route('GET', '/api/workflow/status', workflowRoutes.getStatus), + route('GET', '/api/workflow/guide', workflowRoutes.getGuide), + route('GET', '/api/workflow/plans', workflowRoutes.getPlans), + route('GET', '/api/workflow/plans/:slug', workflowRoutes.getPlanDetails), + route('GET', '/api/workflow/harness', workflowRoutes.getHarnessStatus), + + route('GET', '/api/events', eventsRoutes.streamEvents), + ]; +} + +export function matchRoute( + routes: RouteDefinition[], + method: string, + pathname: string +): { handler: RouteHandler; params: RouteParams } | null { + const requestSegments = splitPath(pathname); + + for (const candidate of routes) { + if (candidate.method !== method) { + continue; + } + if (candidate.segments.length !== requestSegments.length) { + continue; + } + + const params: RouteParams = {}; + let matched = true; + + for (let i = 0; i < candidate.segments.length; i++) { + const routeSegment = candidate.segments[i]; + const requestSegment = requestSegments[i]; + + if (routeSegment.startsWith(':')) { + params[routeSegment.slice(1)] = decodeURIComponent(requestSegment); + } else if (routeSegment !== requestSegment) { + matched = false; + break; + } + } + + if (matched) { + return { handler: candidate.handler, params }; + } + } + + return null; +} + +export type RouteDispatcher = ( + req: IncomingMessage, + res: ServerResponse, + pathname: string +) => Promise; + +/** + * Builds a dispatcher bound to a given `RouteContext`. Returns `true` if the + * request matched an `/api/*` route (and was handled), `false` otherwise so + * the caller (`server.ts`) can fall through to static asset serving. + */ +export function createRouter(ctx: RouteContext): RouteDispatcher { + const routes = buildRoutes(); + + return async function dispatch( + req: IncomingMessage, + res: ServerResponse, + pathname: string + ): Promise { + const method = (req.method || 'GET').toUpperCase(); + const match = matchRoute(routes, method, pathname); + + if (!match) { + return false; + } + + try { + await match.handler(req, res, match.params, ctx); + } catch (error) { + if (!res.headersSent) { + sendError(res, 500, errorMessage(error)); + } else { + res.end(); + } + } + + return true; + }; +} diff --git a/src/web/routes/agents.ts b/src/web/routes/agents.ts new file mode 100644 index 00000000..96a221f2 --- /dev/null +++ b/src/web/routes/agents.ts @@ -0,0 +1,34 @@ +/** + * Agents Routes — `GET /api/agents`, `GET /api/agents/:type` + * + * Thin transport wrapper over `HarnessAgentActionService`. See + * `.context/docs/web-interface-architecture.md` section 4.3. + */ + +import { HarnessAgentActionService, type HarnessAgentActionInput } from '../../harness'; +import { sendData, sendError, errorMessage } from '../response'; +import type { RouteHandler } from '../router'; + +export const listAgents: RouteHandler = async (_req, res, _params, ctx) => { + try { + const service = new HarnessAgentActionService({ repoPath: ctx.repoPath }); + const result = await service.execute({ action: 'discover' }); + sendData(res, 200, result); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; + +export const getAgent: RouteHandler = async (_req, res, params, ctx) => { + try { + const service = new HarnessAgentActionService({ repoPath: ctx.repoPath }); + const agent = params.type as HarnessAgentActionInput['agent']; + const [info, docs] = await Promise.all([ + service.execute({ action: 'getInfo', agentType: params.type }), + service.execute({ action: 'getDocs', agent }), + ]); + sendData(res, 200, { info, docs }); + } catch (error) { + sendError(res, 404, errorMessage(error)); + } +}; diff --git a/src/web/routes/docs.ts b/src/web/routes/docs.ts new file mode 100644 index 00000000..1ce7ba28 --- /dev/null +++ b/src/web/routes/docs.ts @@ -0,0 +1,31 @@ +/** + * Docs Routes — `GET /api/docs`, `GET /api/docs/:name` + * + * Thin transport wrapper over `HarnessDocsService` + * (`src/harness/application/docs`). See + * `.context/docs/web-interface-architecture.md` section 4.1. + */ + +import { HarnessDocsService } from '../../harness'; +import { sendData, sendError, errorMessage } from '../response'; +import type { RouteHandler } from '../router'; + +export const listDocs: RouteHandler = async (_req, res, _params, ctx) => { + try { + const service = new HarnessDocsService({ repoPath: ctx.repoPath }); + const docs = await service.list(); + sendData(res, 200, docs); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; + +export const getDoc: RouteHandler = async (_req, res, params, ctx) => { + try { + const service = new HarnessDocsService({ repoPath: ctx.repoPath }); + const doc = await service.getContent(params.name); + sendData(res, 200, doc); + } catch (error) { + sendError(res, 404, errorMessage(error)); + } +}; diff --git a/src/web/routes/events.ts b/src/web/routes/events.ts new file mode 100644 index 00000000..b4e24dff --- /dev/null +++ b/src/web/routes/events.ts @@ -0,0 +1,48 @@ +/** + * Events Route — `GET /api/events` (SSE) + * + * See `.context/docs/web-interface-architecture.md` section 4.6. Emits a + * `hello` event with the server timestamp on connect, then a + * `runtime-change` event (`{ paths: string[] }`) per debounced batch from + * the shared `RuntimeWatcher` (one watcher instance per `src/web` process, + * shared across every connected client — see `server.ts`). + * + * The payload is intentionally coarse: it tells the client *something* + * under `.context/runtime` changed, never which resource. Clients must + * treat any event (and reconnect) as "refetch the active view's REST data". + */ + +import type { ServerResponse } from 'http'; + +import type { RouteHandler } from '../router'; +import type { RuntimeChangeEvent } from '../events/runtimeWatcher'; + +function writeEvent(res: ServerResponse, event: string, data: unknown): void { + res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); +} + +export const streamEvents: RouteHandler = (req, res, _params, ctx) => { + res.writeHead(200, { + 'Content-Type': 'text/event-stream; charset=utf-8', + 'Cache-Control': 'no-cache, no-transform', + Connection: 'keep-alive', + }); + + // Flush headers / open the stream immediately. + res.write('\n'); + + writeEvent(res, 'hello', { serverTime: new Date().toISOString() }); + + const onRuntimeChange = (change: RuntimeChangeEvent): void => { + writeEvent(res, 'runtime-change', change); + }; + + ctx.runtimeWatcher.on('runtime-change', onRuntimeChange); + + const cleanup = (): void => { + ctx.runtimeWatcher.off('runtime-change', onRuntimeChange); + }; + + req.on('close', cleanup); + res.on('close', cleanup); +}; diff --git a/src/web/routes/sessions.ts b/src/web/routes/sessions.ts new file mode 100644 index 00000000..46edf8a2 --- /dev/null +++ b/src/web/routes/sessions.ts @@ -0,0 +1,61 @@ +/** + * Sessions Routes — `GET /api/sessions`, `GET /api/sessions/:id`, + * `.../traces`, `.../artifacts`, `.../checkpoints` + * + * Thin transport wrapper over `HarnessRuntimeStateService`. See + * `.context/docs/web-interface-architecture.md` section 4.4. Read-only in + * this phase — no create/checkpoint/append routes are exposed over HTTP. + */ + +import { HarnessRuntimeStateService } from '../../harness'; +import { sendData, sendError, errorMessage } from '../response'; +import type { RouteContext, RouteHandler } from '../router'; + +function runtimeStateService(ctx: RouteContext): HarnessRuntimeStateService { + return new HarnessRuntimeStateService({ repoPath: ctx.repoPath }); +} + +export const listSessions: RouteHandler = async (_req, res, _params, ctx) => { + try { + const sessions = await runtimeStateService(ctx).listSessions(); + sendData(res, 200, sessions); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; + +export const getSession: RouteHandler = async (_req, res, params, ctx) => { + try { + const session = await runtimeStateService(ctx).getSession(params.id); + sendData(res, 200, session); + } catch (error) { + sendError(res, 404, errorMessage(error)); + } +}; + +export const listTraces: RouteHandler = async (_req, res, params, ctx) => { + try { + const traces = await runtimeStateService(ctx).listTraces(params.id); + sendData(res, 200, traces); + } catch (error) { + sendError(res, 404, errorMessage(error)); + } +}; + +export const listArtifacts: RouteHandler = async (_req, res, params, ctx) => { + try { + const artifacts = await runtimeStateService(ctx).listArtifacts(params.id); + sendData(res, 200, artifacts); + } catch (error) { + sendError(res, 404, errorMessage(error)); + } +}; + +export const listCheckpoints: RouteHandler = async (_req, res, params, ctx) => { + try { + const checkpoints = await runtimeStateService(ctx).listCheckpoints(params.id); + sendData(res, 200, checkpoints); + } catch (error) { + sendError(res, 404, errorMessage(error)); + } +}; diff --git a/src/web/routes/skills.ts b/src/web/routes/skills.ts new file mode 100644 index 00000000..5b82e005 --- /dev/null +++ b/src/web/routes/skills.ts @@ -0,0 +1,36 @@ +/** + * Skills Routes — `GET /api/skills`, `GET /api/skills/:slug` + * + * Thin transport wrapper over `HarnessSkillActionService` (which wraps + * `HarnessSkillsService`). Underlying results already use an ad hoc + * `{ success, ... }` shape meant for MCP tool results; per the architecture + * doc (section 4.2) those are passed through as-is inside the `data` + * envelope rather than reshaped, since they never throw for a "not found" + * skill (they return `{ success: false, error }` instead). + */ + +import { HarnessSkillActionService } from '../../harness'; +import { sendData, sendError, errorMessage } from '../response'; +import type { RouteHandler } from '../router'; + +export const listSkills: RouteHandler = async (req, res, _params, ctx) => { + try { + const url = new URL(req.url ?? '/', 'http://localhost'); + const includeContent = url.searchParams.get('content') === 'true'; + const service = new HarnessSkillActionService({ repoPath: ctx.repoPath }); + const result = await service.execute({ action: 'list', includeContent }); + sendData(res, 200, result); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; + +export const getSkill: RouteHandler = async (_req, res, params, ctx) => { + try { + const service = new HarnessSkillActionService({ repoPath: ctx.repoPath }); + const result = await service.execute({ action: 'getContent', skillSlug: params.slug }); + sendData(res, 200, result); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; diff --git a/src/web/routes/workflow.ts b/src/web/routes/workflow.ts new file mode 100644 index 00000000..de3782d1 --- /dev/null +++ b/src/web/routes/workflow.ts @@ -0,0 +1,75 @@ +/** + * Workflow Routes — `GET /api/workflow/status`, `.../guide`, `.../plans`, + * `.../plans/:slug`, `.../harness` + * + * Thin transport wrapper over `WorkflowService`, `WorkflowGuideService`, and + * `HarnessPlansService`. See + * `.context/docs/web-interface-architecture.md` section 4.5. + * + * `getHarnessStatus` calls `WorkflowService.getHarnessStatus()` (no + * argument), which already resolves the active workflow's name via + * `getSummary()` and delegates to + * `HarnessSessionFacade.getHarnessStatus(workflowName)` internally — the + * same call chain the architecture doc describes, without this route having + * to re-derive the workflow name itself. + */ + +import { WorkflowService, WorkflowGuideService, HarnessPlansService } from '../../harness'; +import { sendData, sendError, errorMessage } from '../response'; +import type { RouteHandler } from '../router'; + +export const getStatus: RouteHandler = async (_req, res, _params, ctx) => { + try { + const service = new WorkflowService(ctx.repoPath); + + if (!(await service.hasWorkflow())) { + sendData(res, 200, { status: null, summary: null }); + return; + } + + const [status, summary] = await Promise.all([service.getStatus(), service.getSummary()]); + sendData(res, 200, { status, summary }); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; + +export const getGuide: RouteHandler = async (_req, res, _params, ctx) => { + try { + const service = new WorkflowGuideService({ repoPath: ctx.repoPath }); + const guide = await service.guide({ intent: 'session_start' }); + sendData(res, 200, guide); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; + +export const getPlans: RouteHandler = async (_req, res, _params, ctx) => { + try { + const service = new HarnessPlansService({ repoPath: ctx.repoPath }); + const plans = await service.getLinked(); + sendData(res, 200, plans); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; + +export const getPlanDetails: RouteHandler = async (_req, res, params, ctx) => { + try { + const service = new HarnessPlansService({ repoPath: ctx.repoPath }); + const details = await service.getDetails(params.slug); + sendData(res, 200, details); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; + +export const getHarnessStatus: RouteHandler = async (_req, res, _params, ctx) => { + try { + const service = new WorkflowService(ctx.repoPath); + const harnessStatus = await service.getHarnessStatus(); + sendData(res, 200, harnessStatus); + } catch (error) { + sendError(res, 500, errorMessage(error)); + } +}; diff --git a/src/web/server.ts b/src/web/server.ts new file mode 100644 index 00000000..e911ce40 --- /dev/null +++ b/src/web/server.ts @@ -0,0 +1,190 @@ +/** + * Web Server + * + * Plain `node:http` server + the `src/web/router.ts` dispatcher (ADR-1, see + * `.context/docs/web-interface-architecture.md` section 2). Two + * responsibilities: + * + * 1. Serve the `/api/*` REST + SSE contract (section 4), delegating to the + * router. + * 2. Serve the built `web-ui/dist` SPA as static assets, falling back to + * `index.html` for any non-`/api/*` path that doesn't match a static + * file (client-side routing). + * + * Binds `127.0.0.1` by default; binding elsewhere requires an explicit + * `host` and logs a warning (section 4.7 — "no auth, localhost-only" is the + * accepted default security posture). + */ + +import * as http from 'http'; +import * as path from 'path'; +import * as fs from 'fs-extra'; + +import { PathValidator } from '../utils/pathSecurity'; +import { createRuntimeWatcher, type RuntimeWatcher } from './events/runtimeWatcher'; +import { createRouter, type RouteDispatcher } from './router'; +import { sendError, errorMessage } from './response'; + +export const DEFAULT_WEB_HOST = '127.0.0.1'; +export const DEFAULT_WEB_PORT = 4317; + +/** + * The built frontend (`web-ui/dist`) ships alongside this package, not + * inside the *target* repository (`repoPath`). Resolved relative to this + * module so it works the same way under `tsx`/`ts-jest` (running from + * `src/web/server.ts`) and the compiled CLI (`dist/web/server.js`): both are + * two directories below the repo/package root. + */ +export function resolveWebUiDistDir(): string { + return path.resolve(__dirname, '..', '..', 'web-ui', 'dist'); +} + +export interface StartWebServerOptions { + repoPath: string; + port?: number; + host?: string; +} + +export interface WebServerHandle { + server: http.Server; + host: string; + port: number; + url: string; + stop(): Promise; +} + +const STATIC_CONTENT_TYPES: Record = { + '.html': 'text/html; charset=utf-8', + '.js': 'text/javascript; charset=utf-8', + '.mjs': 'text/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.ico': 'image/x-icon', + '.map': 'application/json; charset=utf-8', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.txt': 'text/plain; charset=utf-8', +}; + +async function serveStatic(res: http.ServerResponse, pathname: string, distDir: string): Promise { + if (!(await fs.pathExists(distDir))) { + res.writeHead(503, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('dotcontext web UI is not built. Run "npm run build:web-ui" first.'); + return; + } + + const validator = new PathValidator(distDir); + const requested = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, ''); + + let filePath: string | null = validator.safeResolve(requested); + let stat = filePath ? await fs.stat(filePath).catch(() => null) : null; + + if (!filePath || !stat || stat.isDirectory()) { + // SPA fallback: any non-API path that isn't a real static file resolves + // to index.html so client-side routing (react-router) can take over. + filePath = path.join(distDir, 'index.html'); + stat = await fs.stat(filePath).catch(() => null); + } + + if (!stat) { + res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Not found'); + return; + } + + const contentType = STATIC_CONTENT_TYPES[path.extname(filePath)] ?? 'application/octet-stream'; + const content = await fs.readFile(filePath); + res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': content.length }); + res.end(content); +} + +async function handleRequest( + req: http.IncomingMessage, + res: http.ServerResponse, + routeRequest: RouteDispatcher, + distDir: string +): Promise { + try { + const parsed = new URL(req.url ?? '/', 'http://localhost'); + const pathname = parsed.pathname; + + if (pathname.startsWith('/api/')) { + const matched = await routeRequest(req, res, pathname); + if (!matched) { + sendError(res, 404, `No API route for ${req.method ?? 'GET'} ${pathname}`); + } + return; + } + + await serveStatic(res, pathname, distDir); + } catch (error) { + if (!res.headersSent) { + sendError(res, 500, errorMessage(error)); + } else { + res.end(); + } + } +} + +export async function startWebServer(options: StartWebServerOptions): Promise { + const repoPath = path.resolve(options.repoPath); + const host = options.host ?? DEFAULT_WEB_HOST; + const requestedPort = options.port ?? DEFAULT_WEB_PORT; + const distDir = resolveWebUiDistDir(); + + if (host !== DEFAULT_WEB_HOST) { + // eslint-disable-next-line no-console + console.warn( + `[dotcontext web] Binding to ${host} instead of ${DEFAULT_WEB_HOST}. ` + + 'The web dashboard has no authentication; only do this on a trusted network.' + ); + } + + let runtimeWatcher: RuntimeWatcher | undefined; + + try { + runtimeWatcher = createRuntimeWatcher({ repoPath }); + const routeRequest = createRouter({ repoPath, runtimeWatcher }); + + const server = http.createServer((req, res) => { + void handleRequest(req, res, routeRequest, distDir); + }); + + await new Promise((resolve, reject) => { + const onError = (error: Error): void => { + server.off('listening', onListening); + reject(error); + }; + const onListening = (): void => { + server.off('error', onError); + resolve(); + }; + server.once('error', onError); + server.once('listening', onListening); + server.listen(requestedPort, host); + }); + + const address = server.address(); + const port = typeof address === 'object' && address ? address.port : requestedPort; + + return { + server, + host, + port, + url: `http://${host}:${port}`, + async stop(): Promise { + await runtimeWatcher?.close(); + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + }, + }; + } catch (error) { + await runtimeWatcher?.close(); + throw error; + } +} diff --git a/templates/packages/cli.README.md b/templates/packages/cli.README.md index 8d39d94d..d8d9fcc2 100644 --- a/templates/packages/cli.README.md +++ b/templates/packages/cli.README.md @@ -8,5 +8,15 @@ This package owns: - local operator workflows - MCP installation into supported tools - sync, reverse-sync, import/export, and workflow UX +- the bundled local web dashboard (`dotcontext web`) It depends on the harness and MCP boundaries but is the user-facing entrypoint. + +## Web dashboard + +```bash +dotcontext web +dotcontext web --no-open +``` + +The package includes the built React assets under `web-ui/dist`; users do not need the source `web-ui/` workspace to run the dashboard. diff --git a/web-ui/.gitignore b/web-ui/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/web-ui/.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/web-ui/.oxlintrc.json b/web-ui/.oxlintrc.json new file mode 100644 index 00000000..6fa991da --- /dev/null +++ b/web-ui/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/web-ui/README.md b/web-ui/README.md new file mode 100644 index 00000000..f24fe3a6 --- /dev/null +++ b/web-ui/README.md @@ -0,0 +1,68 @@ +# dotcontext Web UI + +React + Vite dashboard for the local `src/web` API. It renders `.context` docs, skills, agents, sessions, traces, artifacts, checkpoints, and PREVC workflow state. + +## Development + +From the repository root: + +```bash +npm install +npm --prefix web-ui install +npm run dev:web +``` + +This starts: + +- `dotcontext web --api-only --no-open` on `http://127.0.0.1:4317` +- Vite on `http://localhost:5173` + +Open the Vite URL for HMR. Vite proxies `/api/*` and `/api/events` to the dotcontext web API. If the API port changes, set `VITE_API_PROXY_TARGET`. + +```bash +VITE_API_PROXY_TARGET=http://127.0.0.1:4399 npm run dev:web-ui +``` + +You can also run the pieces separately: + +```bash +npm run dev:web-api +npm run dev:web-ui +``` + +## Production Build + +Build the static SPA: + +```bash +npm run build:web-ui +``` + +Then serve it through the CLI: + +```bash +npm run build +node dist/index.js web --no-open +``` + +For an installed package, run: + +```bash +dotcontext web +``` + +The published `@dotcontext/cli` package includes `web-ui/dist`, so installed users do not need Vite or the `web-ui` source tree. + +## Validation + +Useful checks while changing the dashboard: + +```bash +npm --prefix web-ui run build +npm run build +npm test -- --runInBand +npm run build:packages +npm run smoke:packages +``` + +`build:packages` rebuilds `web-ui/dist` and copies it into `.release/packages/cli/web-ui/dist`. `smoke:packages` verifies that bundle before release. diff --git a/web-ui/index.html b/web-ui/index.html new file mode 100644 index 00000000..b81219aa --- /dev/null +++ b/web-ui/index.html @@ -0,0 +1,13 @@ + + + + + + + dotcontext — Web Dashboard + + +
+ + + diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json new file mode 100644 index 00000000..75ec7e2c --- /dev/null +++ b/web-ui/package-lock.json @@ -0,0 +1,2919 @@ +{ + "name": "@dotcontext/web-ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@dotcontext/web-ui", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.18.1", + "remark-gfm": "^4.0.1" + }, + "devDependencies": { + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.72.0.tgz", + "integrity": "sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.72.0.tgz", + "integrity": "sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.72.0.tgz", + "integrity": "sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.72.0.tgz", + "integrity": "sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.72.0.tgz", + "integrity": "sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.72.0.tgz", + "integrity": "sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.72.0.tgz", + "integrity": "sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.72.0.tgz", + "integrity": "sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.72.0.tgz", + "integrity": "sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.72.0.tgz", + "integrity": "sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.72.0.tgz", + "integrity": "sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.72.0.tgz", + "integrity": "sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.72.0.tgz", + "integrity": "sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.72.0.tgz", + "integrity": "sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.72.0.tgz", + "integrity": "sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.72.0.tgz", + "integrity": "sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.72.0.tgz", + "integrity": "sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.72.0.tgz", + "integrity": "sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.72.0.tgz", + "integrity": "sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oxlint": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.72.0.tgz", + "integrity": "sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.72.0", + "@oxlint/binding-android-arm64": "1.72.0", + "@oxlint/binding-darwin-arm64": "1.72.0", + "@oxlint/binding-darwin-x64": "1.72.0", + "@oxlint/binding-freebsd-x64": "1.72.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.72.0", + "@oxlint/binding-linux-arm-musleabihf": "1.72.0", + "@oxlint/binding-linux-arm64-gnu": "1.72.0", + "@oxlint/binding-linux-arm64-musl": "1.72.0", + "@oxlint/binding-linux-ppc64-gnu": "1.72.0", + "@oxlint/binding-linux-riscv64-gnu": "1.72.0", + "@oxlint/binding-linux-riscv64-musl": "1.72.0", + "@oxlint/binding-linux-s390x-gnu": "1.72.0", + "@oxlint/binding-linux-x64-gnu": "1.72.0", + "@oxlint/binding-linux-x64-musl": "1.72.0", + "@oxlint/binding-openharmony-arm64": "1.72.0", + "@oxlint/binding-win32-arm64-msvc": "1.72.0", + "@oxlint/binding-win32-ia32-msvc": "1.72.0", + "@oxlint/binding-win32-x64-msvc": "1.72.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.22.1", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-router": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.2.tgz", + "integrity": "sha512-6YYPbRXTxx6bRXmOn7XdnQAy5DQNHhDgtjhDHI13oe4pY93kkcdGJWxpGwOm++/Wh0QpQhDrpIoVMrmrsI5AGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/web-ui/package.json b/web-ui/package.json new file mode 100644 index 00000000..6022932c --- /dev/null +++ b/web-ui/package.json @@ -0,0 +1,29 @@ +{ + "name": "@dotcontext/web-ui", + "private": true, + "version": "0.0.0", + "description": "Vite + React dashboard SPA for the dotcontext src/web API (docs, skills, agents, sessions, workflow).", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.18.1", + "remark-gfm": "^4.0.1" + }, + "devDependencies": { + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1" + } +} diff --git a/web-ui/pnpm-lock.yaml b/web-ui/pnpm-lock.yaml new file mode 100644 index 00000000..6720a0df --- /dev/null +++ b/web-ui/pnpm-lock.yaml @@ -0,0 +1,1738 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + react: + specifier: ^19.2.7 + version: 19.2.7 + react-dom: + specifier: ^19.2.7 + version: 19.2.7(react@19.2.7) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.17)(react@19.2.7) + react-router-dom: + specifier: ^7.18.1 + version: 7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + devDependencies: + '@types/node': + specifier: ^24.13.2 + version: 24.13.2 + '@types/react': + specifier: ^19.2.17 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: ^6.0.3 + version: 6.0.3(vite@8.1.2(@types/node@24.13.2)) + oxlint: + specifier: ^1.71.0 + version: 1.72.0 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + vite: + specifier: ^8.1.1 + version: 8.1.2(@types/node@24.13.2) + +packages: + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + + '@oxlint/binding-android-arm-eabi@1.72.0': + resolution: {integrity: sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.72.0': + resolution: {integrity: sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.72.0': + resolution: {integrity: sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.72.0': + resolution: {integrity: sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.72.0': + resolution: {integrity: sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.72.0': + resolution: {integrity: sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.72.0': + resolution: {integrity: sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.72.0': + resolution: {integrity: sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxlint/binding-linux-arm64-musl@1.72.0': + resolution: {integrity: sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxlint/binding-linux-ppc64-gnu@1.72.0': + resolution: {integrity: sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxlint/binding-linux-riscv64-gnu@1.72.0': + resolution: {integrity: sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-riscv64-musl@1.72.0': + resolution: {integrity: sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-s390x-gnu@1.72.0': + resolution: {integrity: sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxlint/binding-linux-x64-gnu@1.72.0': + resolution: {integrity: sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxlint/binding-linux-x64-musl@1.72.0': + resolution: {integrity: sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxlint/binding-openharmony-arm64@1.72.0': + resolution: {integrity: sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.72.0': + resolution: {integrity: sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.72.0': + resolution: {integrity: sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.72.0': + resolution: {integrity: sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/binding-android-arm64@1.1.3': + resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.3': + resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.3': + resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.3': + resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.3': + resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.1.3': + resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.1.3': + resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.1.3': + resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.1.3': + resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.1.3': + resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.3': + resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.3': + resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.3': + resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@24.13.2': + resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.2': + resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} + + '@vitejs/plugin-react@6.0.3': + resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + 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==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + oxlint@1.72.0: + resolution: {integrity: sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.22.1' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-router-dom@7.18.1: + resolution: {integrity: sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.18.1: + resolution: {integrity: sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + rolldown@1.1.3: + resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + 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==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + 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.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@8.1.2: + resolution: {integrity: sha512-6YYPbRXTxx6bRXmOn7XdnQAy5DQNHhDgtjhDHI13oe4pY93kkcdGJWxpGwOm++/Wh0QpQhDrpIoVMrmrsI5AGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@oxc-project/types@0.137.0': {} + + '@oxlint/binding-android-arm-eabi@1.72.0': + optional: true + + '@oxlint/binding-android-arm64@1.72.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.72.0': + optional: true + + '@oxlint/binding-darwin-x64@1.72.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.72.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.72.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.72.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.72.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.72.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.72.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.72.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.72.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.72.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.72.0': + optional: true + + '@rolldown/binding-android-arm64@1.1.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.3': + optional: true + + '@rolldown/binding-darwin-x64@1.1.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.3': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + + '@types/estree@1.0.9': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/node@24.13.2': + dependencies: + undici-types: 7.18.2 + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.2': {} + + '@vitejs/plugin-react@6.0.3(vite@8.1.2(@types/node@24.13.2))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.1.2(@types/node@24.13.2) + + bail@2.0.2: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + comma-separated-tokens@2.0.3: {} + + cookie@1.1.1: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + escape-string-regexp@5.0.0: {} + + estree-util-is-identifier-name@3.0.0: {} + + extend@3.0.2: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fsevents@2.3.3: + optional: true + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + html-url-attributes@3.0.1: {} + + inline-style-parser@0.2.7: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-hexadecimal@2.0.1: {} + + is-plain-obj@4.1.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + longest-streak@3.1.0: {} + + markdown-table@3.0.4: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.2 + 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.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + 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-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + ms@2.1.3: {} + + nanoid@3.3.15: {} + + oxlint@1.72.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.72.0 + '@oxlint/binding-android-arm64': 1.72.0 + '@oxlint/binding-darwin-arm64': 1.72.0 + '@oxlint/binding-darwin-x64': 1.72.0 + '@oxlint/binding-freebsd-x64': 1.72.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.72.0 + '@oxlint/binding-linux-arm-musleabihf': 1.72.0 + '@oxlint/binding-linux-arm64-gnu': 1.72.0 + '@oxlint/binding-linux-arm64-musl': 1.72.0 + '@oxlint/binding-linux-ppc64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-musl': 1.72.0 + '@oxlint/binding-linux-s390x-gnu': 1.72.0 + '@oxlint/binding-linux-x64-gnu': 1.72.0 + '@oxlint/binding-linux-x64-musl': 1.72.0 + '@oxlint/binding-openharmony-arm64': 1.72.0 + '@oxlint/binding-win32-arm64-msvc': 1.72.0 + '@oxlint/binding-win32-ia32-msvc': 1.72.0 + '@oxlint/binding-win32-x64-msvc': 1.72.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + property-information@7.2.0: {} + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-markdown@10.1.0(@types/react@19.2.17)(react@19.2.7): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.17 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.7 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react-router-dom@7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-router: 7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + + react-router@7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + cookie: 1.1.1 + react: 19.2.7 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.7(react@19.2.7) + + react@19.2.7: {} + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + rolldown@1.1.3: + dependencies: + '@oxc-project/types': 0.137.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.3 + '@rolldown/binding-darwin-arm64': 1.1.3 + '@rolldown/binding-darwin-x64': 1.1.3 + '@rolldown/binding-freebsd-x64': 1.1.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.3 + '@rolldown/binding-linux-arm64-gnu': 1.1.3 + '@rolldown/binding-linux-arm64-musl': 1.1.3 + '@rolldown/binding-linux-ppc64-gnu': 1.1.3 + '@rolldown/binding-linux-s390x-gnu': 1.1.3 + '@rolldown/binding-linux-x64-gnu': 1.1.3 + '@rolldown/binding-linux-x64-musl': 1.1.3 + '@rolldown/binding-openharmony-arm64': 1.1.3 + '@rolldown/binding-wasm32-wasi': 1.1.3 + '@rolldown/binding-win32-arm64-msvc': 1.1.3 + '@rolldown/binding-win32-x64-msvc': 1.1.3 + + scheduler@0.27.0: {} + + set-cookie-parser@2.7.2: {} + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tslib@2.8.1: + optional: true + + typescript@6.0.3: {} + + undici-types@7.18.2: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + 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.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + vfile-message@4.0.3: + 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.3 + + vite@8.1.2(@types/node@24.13.2): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.16 + rolldown: 1.1.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.2 + fsevents: 2.3.3 + + zwitch@2.0.4: {} diff --git a/web-ui/public/favicon.svg b/web-ui/public/favicon.svg new file mode 100644 index 00000000..6893eb13 --- /dev/null +++ b/web-ui/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web-ui/src/App.css b/web-ui/src/App.css new file mode 100644 index 00000000..fe300543 --- /dev/null +++ b/web-ui/src/App.css @@ -0,0 +1,685 @@ +/* Layout shell -------------------------------------------------------- */ + +.app-shell { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 232px; + flex: none; + display: flex; + flex-direction: column; + border-right: 1px solid var(--border); + padding: 18px 14px; + background: var(--surface); +} + +.sidebar-header { + margin-bottom: 20px; + padding: 4px 8px 12px; + border-bottom: 1px solid var(--border); +} + +.sidebar-title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; + font-size: 1.05rem; + color: var(--text-h); +} + +.sidebar-title-mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + border-radius: 8px; + background: var(--accent); + color: #fff; + font-size: 0.8rem; + font-weight: 700; +} + +.sidebar-subtitle { + display: block; + font-size: 0.74rem; + color: var(--text-muted); + margin-top: 4px; +} + +.sidebar-nav { + display: flex; + flex-direction: column; + gap: 2px; +} + +.sidebar-link { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: var(--radius-sm); + text-decoration: none; + color: var(--text); + font-size: 0.88rem; + font-weight: 500; + transition: background-color 0.12s ease, color 0.12s ease; +} + +.sidebar-link svg { + flex: none; + opacity: 0.75; +} + +.sidebar-link:hover { + background: var(--surface-sunken); +} + +.sidebar-link--active { + background: var(--accent-soft); + color: var(--accent-hover); +} + +.sidebar-link--active svg { + opacity: 1; +} + +.sidebar-footer { + margin-top: auto; + padding: 10px 8px 0; + border-top: 1px solid var(--border); +} + +.connection-badge { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 0.74rem; + color: var(--text-muted); +} + +.connection-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--text-faint); +} + +.connection-badge--open .connection-dot { + background: var(--success); +} + +.connection-badge--connecting .connection-dot { + background: #d18a00; +} + +.connection-badge--error .connection-dot { + background: var(--danger); +} + +.content { + flex: 1 1 auto; + padding: 22px 30px 40px; + overflow-y: auto; + min-width: 0; +} + +/* Page header ---------------------------------------------------------- */ + +.page-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 18px; + flex-wrap: wrap; +} + +.page-header-heading h1 { + margin-bottom: 2px; +} + +.page-header-actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +/* Buttons ---------------------------------------------------------------*/ + +.btn { + display: inline-flex; + align-items: center; + gap: 6px; + font: inherit; + font-size: 0.82rem; + font-weight: 500; + padding: 6px 12px; + border-radius: var(--radius-sm); + border: 1px solid var(--border-strong); + background: var(--surface); + color: var(--text); + cursor: pointer; + transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease; + white-space: nowrap; +} + +.btn:hover { + background: var(--surface-sunken); +} + +.btn:active { + transform: translateY(0.5px); +} + +.btn svg { + flex: none; +} + +.btn--primary { + background: var(--accent); + border-color: var(--accent); + color: #fff; +} + +.btn--primary:hover { + background: var(--accent-hover); + border-color: var(--accent-hover); +} + +.btn--ghost { + border-color: transparent; + background: transparent; +} + +.btn--ghost:hover { + background: var(--surface-sunken); +} + +.btn--sm { + padding: 4px 9px; + font-size: 0.76rem; +} + +.btn--success { + border-color: var(--success); + color: var(--success); + background: var(--success-soft); +} + +/* Search input ----------------------------------------------------------*/ + +.search-input-wrap { + position: relative; + display: inline-flex; + align-items: center; +} + +.search-input-wrap svg { + position: absolute; + left: 9px; + opacity: 0.5; + pointer-events: none; +} + +.search-input { + font: inherit; + font-size: 0.84rem; + padding: 6px 10px 6px 30px; + border-radius: var(--radius-sm); + border: 1px solid var(--border-strong); + background: var(--surface); + color: var(--text); + min-width: 200px; +} + +.search-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); +} + +/* Two-pane list/detail --------------------------------------------------*/ + +.two-pane { + display: grid; + grid-template-columns: 290px 1fr; + gap: 20px; + align-items: start; +} + +.list-panel { + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 12px; + background: var(--surface); + box-shadow: var(--shadow-sm); +} + +.panel-heading { + margin: 0 0 4px; + font-size: 0.95rem; + display: flex; + align-items: center; + justify-content: space-between; +} + +.panel-count { + font-size: 0.72rem; + font-weight: 500; + color: var(--text-faint); + background: var(--surface-sunken); + border-radius: 999px; + padding: 1px 8px; +} + +.list-panel .search-input-wrap { + display: flex; + width: 100%; + margin: 8px 0 10px; +} + +.list-panel .search-input { + width: 100%; + min-width: 0; +} + +.entry-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 3px; + max-height: calc(100vh - 220px); + overflow-y: auto; +} + +.entry-button { + width: 100%; + text-align: left; + border: 1px solid transparent; + border-left: 2px solid transparent; + background: transparent; + border-radius: var(--radius-sm); + padding: 8px 9px; + cursor: pointer; + display: flex; + flex-direction: column; + gap: 2px; + font: inherit; +} + +.entry-button:hover { + background: var(--surface-sunken); +} + +.entry-button--active { + background: var(--accent-soft); + border-left-color: var(--accent); +} + +.entry-title { + font-weight: 600; + font-size: 0.87rem; + color: var(--text-h); +} + +.entry-subtitle { + font-size: 0.76rem; + color: var(--text-muted); +} + +.entry-badge { + font-size: 0.68rem; + color: var(--text-faint); + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.detail-panel { + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 20px 24px; + background: var(--surface); + min-height: 200px; + box-shadow: var(--shadow-sm); +} + +.detail-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 6px; + flex-wrap: wrap; +} + +.detail-header-actions { + display: flex; + gap: 6px; + flex: none; +} + +.tag-row { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 8px 0 12px; +} + +.tag { + display: inline-block; + padding: 2px 9px; + border-radius: 999px; + font-size: 0.72rem; + background: var(--surface-sunken); + color: var(--text-muted); + border: 1px solid var(--border); +} + +.markdown-content { + line-height: 1.6; +} + +.markdown-content pre { + background: var(--surface-sunken); + padding: 12px 14px; + border-radius: var(--radius-sm); + overflow-x: auto; +} + +.markdown-content table { + border-collapse: collapse; +} + +.markdown-content th, +.markdown-content td { + border: 1px solid var(--border); + padding: 4px 8px; +} + +.muted { + color: var(--text-muted); +} + +.muted.small, +.small { + font-size: 0.8rem; +} + +.error-note { + color: var(--danger); +} + +/* Pills / badges ---------------------------------------------------------*/ + +.pill { + display: inline-block; + padding: 1px 9px; + border-radius: 999px; + font-size: 0.72rem; + font-weight: 500; + background: var(--surface-sunken); + color: var(--text-muted); + text-transform: capitalize; + border: 1px solid var(--border); +} + +.pill--active, +.pill--passed, +.pill--completed, +.pill--clear { + background: var(--success-soft); + color: var(--success); + border-color: transparent; +} + +.pill--failed, +.pill--blocked, +.pill--error { + background: var(--danger-soft); + color: var(--danger); + border-color: transparent; +} + +.pill--warning, +.pill--in-progress, +.pill--connecting { + background: var(--warning-soft); + color: var(--warning); + border-color: transparent; +} + +.tool-badge { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 2px 9px 2px 7px; + border-radius: 999px; + font-size: 0.72rem; + font-weight: 600; + border: 1px solid var(--accent-soft-border); + background: var(--accent-soft); + color: var(--accent-hover); +} + +.tool-badge svg { + flex: none; +} + +.tool-badge--codex { + background: #e6f7f2; + border-color: #bfe9dc; + color: #0f6b4d; +} + +.tool-badge--pi-dev { + background: #fbeaff; + border-color: #f0cdfb; + color: #862fa3; +} + +.tool-badge--generic, +.tool-badge--unknown { + background: var(--surface-sunken); + border-color: var(--border); + color: var(--text-muted); +} + +.kv-grid { + display: grid; + grid-template-columns: max-content 1fr; + gap: 6px 16px; + margin: 12px 0; + align-items: center; +} + +.kv-grid dt { + font-weight: 600; + color: var(--text-muted); + font-size: 0.85rem; +} + +.kv-grid dd { + margin: 0; +} + +.plain-list { + list-style: none; + margin: 8px 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 6px; +} + +.json-block { + background: var(--surface-sunken); + padding: 12px 14px; + border-radius: var(--radius-sm); + overflow-x: auto; + font-size: 0.8rem; +} + +.trace-list { + list-style: none; + margin: 8px 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 4px; + max-height: 360px; + overflow-y: auto; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.78rem; +} + +.trace-item { + display: grid; + grid-template-columns: 150px 60px 140px 1fr; + gap: 8px; + padding: 4px 7px; + border-radius: var(--radius-sm); +} + +.trace-item--error { + background: var(--danger-soft); +} + +.trace-item--warn { + background: var(--warning-soft); +} + +.trace-level { + text-transform: uppercase; + color: var(--text-muted); +} + +/* Cards / grids (Agents, Skills, lineup) ---------------------------------*/ + +.lineup-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 10px; + margin: 8px 0 4px; +} + +.lineup-card { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 10px 12px; + background: var(--surface-sunken); +} + +.lineup-card-type { + font-weight: 600; + font-size: 0.85rem; + color: var(--text-h); +} + +.lineup-card-role { + font-size: 0.76rem; + color: var(--text-muted); +} + +/* Workflow view -----------------------------------------------------------*/ + +.workflow-view section { + margin-bottom: 28px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 18px 20px; + background: var(--surface); + box-shadow: var(--shadow-sm); +} + +.phase-tracker { + display: flex; + align-items: flex-start; + gap: 0; + margin: 16px 0; +} + +.phase-step { + position: relative; + flex: 1; + text-align: center; + padding: 0 4px; +} + +.phase-step-connector { + position: absolute; + top: 16px; + left: 50%; + width: 100%; + height: 2px; + background: var(--border); + z-index: 0; +} + +.phase-step-marker { + position: relative; + z-index: 1; + width: 32px; + height: 32px; + line-height: 32px; + border-radius: 50%; + background: var(--surface-sunken); + color: var(--text-muted); + font-weight: 700; + margin: 0 auto 6px; +} + +.phase-step--completed .phase-step-marker { + background: var(--success); + color: #fff; +} + +.phase-step--in_progress .phase-step-marker { + background: #d18a00; + color: #fff; +} + +.phase-step--current .phase-step-marker { + outline: 3px solid var(--accent); + outline-offset: 2px; +} + +.phase-step-label { + font-weight: 600; + font-size: 0.85rem; +} + +.phase-step-status { + font-size: 0.72rem; + color: var(--text-muted); + text-transform: capitalize; +} + +.plan-phase-list { + list-style: none; + margin: 8px 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 12px; +} + +.plan-phase { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 10px 12px; + background: var(--surface-sunken); +} + +.plan-phase-header { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} diff --git a/web-ui/src/App.tsx b/web-ui/src/App.tsx new file mode 100644 index 00000000..789e557e --- /dev/null +++ b/web-ui/src/App.tsx @@ -0,0 +1,26 @@ +import { Navigate, Route, Routes } from 'react-router-dom'; +import { Layout } from './components/Layout'; +import { DocsView } from './views/DocsView'; +import { SkillsView } from './views/SkillsView'; +import { AgentsView } from './views/AgentsView'; +import { SessionView } from './views/SessionView'; +import { WorkflowView } from './views/WorkflowView'; +import './App.css'; + +function App() { + return ( + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} + +export default App; diff --git a/web-ui/src/components/Layout.tsx b/web-ui/src/components/Layout.tsx new file mode 100644 index 00000000..406708b4 --- /dev/null +++ b/web-ui/src/components/Layout.tsx @@ -0,0 +1,56 @@ +import type { ReactNode } from 'react'; +import { NavLink } from 'react-router-dom'; +import { useEventStream } from '../hooks/useEventStream'; +import { IconAgents, IconDocs, IconSession, IconSkills, IconWorkflow } from './icons'; + +const NAV_ITEMS = [ + { to: '/docs', label: 'Docs', icon: IconDocs }, + { to: '/skills', label: 'Skills', icon: IconSkills }, + { to: '/agents', label: 'Agents', icon: IconAgents }, + { to: '/session', label: 'Session', icon: IconSession }, + { to: '/workflow', label: 'Workflow', icon: IconWorkflow }, +]; + +function ConnectionBadge() { + const { status } = useEventStream(); + const label = + status === 'open' ? 'Live' : status === 'connecting' ? 'Connecting…' : status === 'error' ? 'Reconnecting…' : 'Offline'; + return ( + + + ); +} + +export function Layout({ children }: { children: ReactNode }) { + return ( +
+ +
{children}
+
+ ); +} diff --git a/web-ui/src/components/common.tsx b/web-ui/src/components/common.tsx new file mode 100644 index 00000000..ae7e6fb3 --- /dev/null +++ b/web-ui/src/components/common.tsx @@ -0,0 +1,237 @@ +import { type ReactNode, useState } from 'react'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { IconCheck, IconCopy, IconDownload, IconSearch, IconTool } from './icons'; + +// --------------------------------------------------------------------------- +// Status / empty / error notes +// --------------------------------------------------------------------------- + +export function LoadingNote({ label = 'Loading…' }: { label?: string }) { + return

{label}

; +} + +export function ErrorNote({ message }: { message: string }) { + return

{message}

; +} + +export function EmptyNote({ label = 'Nothing here yet.' }: { label?: string }) { + return

{label}

; +} + +export function StatusPill({ status }: { status: string }) { + return {status}; +} + +// --------------------------------------------------------------------------- +// Tool / host identification +// +// `session.metadata.host` (set at hook-dispatch time, see +// `src/cli/services/hookDispatchService.ts`) records which coding tool ran +// the workflow: 'claude-code', 'codex', 'pi-dev', or 'generic'. This is the +// only host-identifying signal the harness captures today -- there is no +// per-model tracking (host hook payloads never include the LLM name). +// --------------------------------------------------------------------------- + +const HOST_LABELS: Record = { + 'claude-code': 'Claude Code', + codex: 'Codex', + 'pi-dev': 'Pi', + generic: 'Generic hook', +}; + +export function ToolBadge({ host }: { host?: string | null }) { + const key = host && HOST_LABELS[host] ? host : 'unknown'; + const label = host ? HOST_LABELS[host] ?? host : 'Unknown tool'; + return ( + + + {label} + + ); +} + +// --------------------------------------------------------------------------- +// Copy / download actions +// --------------------------------------------------------------------------- + +export function CopyButton({ text, label = 'Copy' }: { text: string; label?: string }) { + const [copied, setCopied] = useState(false); + + async function handleClick() { + try { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch { + setCopied(false); + } + } + + return ( + + ); +} + +export function DownloadButton({ + content, + filename, + label = 'Download', +}: { + content: string; + filename: string; + label?: string; +}) { + function handleClick() { + const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = filename; + document.body.appendChild(anchor); + anchor.click(); + anchor.remove(); + URL.revokeObjectURL(url); + } + + return ( + + ); +} + +// --------------------------------------------------------------------------- +// Search +// --------------------------------------------------------------------------- + +export function SearchInput({ + value, + onChange, + placeholder = 'Search…', +}: { + value: string; + onChange: (value: string) => void; + placeholder?: string; +}) { + return ( + + + onChange(event.target.value)} + placeholder={placeholder} + /> + + ); +} + +// --------------------------------------------------------------------------- +// Page header +// --------------------------------------------------------------------------- + +export function PageHeader({ + title, + subtitle, + actions, +}: { + title: ReactNode; + subtitle?: ReactNode; + actions?: ReactNode; +}) { + return ( +
+
+

{title}

+ {subtitle &&

{subtitle}

} +
+ {actions &&
{actions}
} +
+ ); +} + +// --------------------------------------------------------------------------- +// List / detail panels +// --------------------------------------------------------------------------- + +export interface ListEntry { + key: string; + title: string; + subtitle?: string; + badge?: string; + meta?: ReactNode; +} + +export function ListPanel({ + title, + entries, + selectedKey, + onSelect, + loading, + error, + search, +}: { + title: string; + entries: ListEntry[]; + selectedKey: string | null; + onSelect: (key: string) => void; + loading?: boolean; + error?: string | null; + search?: { value: string; onChange: (value: string) => void; placeholder?: string }; +}) { + return ( +
+

+ {title} + {entries.length} +

+ {search && } + {loading && } + {error && } + {!loading && !error && entries.length === 0 && } +
    + {entries.map((entry) => ( +
  • + +
  • + ))} +
+
+ ); +} + +export function DetailPanel({ children }: { children: ReactNode }) { + return
{children}
; +} + +export function MarkdownContent({ content }: { content: string }) { + return ( +
+ {content} +
+ ); +} + +export function TwoPaneView({ list, detail }: { list: ReactNode; detail: ReactNode }) { + return ( +
+ {list} + {detail} +
+ ); +} diff --git a/web-ui/src/components/icons.tsx b/web-ui/src/components/icons.tsx new file mode 100644 index 00000000..9abb55ed --- /dev/null +++ b/web-ui/src/components/icons.tsx @@ -0,0 +1,91 @@ +import type { ReactNode } from 'react'; + +type IconProps = { size?: number }; + +function svg(paths: ReactNode) { + return function Icon({ size = 16 }: IconProps) { + return ( + + ); + }; +} + +export const IconCopy = svg( + <> + + + +); + +export const IconCheck = svg(); + +export const IconDownload = svg( + <> + + + + +); + +export const IconSearch = svg( + <> + + + +); + +export const IconDocs = svg( + <> + + + +); + +export const IconSkills = svg( + <> + + +); + +export const IconAgents = svg( + <> + + + + + + +); + +export const IconSession = svg( + <> + + +); + +export const IconWorkflow = svg( + <> + + + + + + + +); + +export const IconTool = svg( + +); diff --git a/web-ui/src/hooks/useApi.ts b/web-ui/src/hooks/useApi.ts new file mode 100644 index 00000000..db0f1f9f --- /dev/null +++ b/web-ui/src/hooks/useApi.ts @@ -0,0 +1,171 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { ApiError, apiFetch } from '../lib/api'; +import { useEventStream } from './useEventStream'; +import type { + AgentDetailResult, + AgentsDiscoverResult, + DocContent, + DocSummary, + HarnessArtifactRecord, + HarnessSessionCheckpoint, + HarnessSessionRecord, + HarnessTraceRecord, + SkillContentResult, + SkillsListResult, + WorkflowGuideResult, + WorkflowHarnessStatus, + WorkflowPlanDetailsResult, + WorkflowPlansResult, + WorkflowStatusResult, +} from '../types/api'; + +export interface ApiResourceState { + data: T | null; + error: string | null; + loading: boolean; + /** True once the very first request for the current `path` has settled. */ + loaded: boolean; + refetch: () => void; +} + +/** + * Generic GET resource hook. + * + * Refetches whenever `path` changes, and whenever the shared SSE stream + * (`useEventStream`) reports a new event/reconnect -- per the section 4.6 + * contract, the SSE payload itself is never trusted, only used as a "go + * refetch REST data" signal. Pass `path: null` to skip fetching (e.g. no + * selection yet). + */ +export function useApiResource(path: string | null): ApiResourceState { + const [data, setData] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(path !== null); + const [loaded, setLoaded] = useState(false); + const { version } = useEventStream(); + const requestId = useRef(0); + + const load = useCallback(() => { + if (path === null) { + setData(null); + setError(null); + setLoading(false); + setLoaded(false); + return; + } + + const id = ++requestId.current; + setLoading(true); + apiFetch(path) + .then((result) => { + if (id !== requestId.current) return; + setData(result); + setError(null); + }) + .catch((err: unknown) => { + if (id !== requestId.current) return; + setError(err instanceof ApiError ? err.message : 'Failed to load data'); + }) + .finally(() => { + if (id !== requestId.current) return; + setLoading(false); + setLoaded(true); + }); + }, [path]); + + // Refetch on path change and on every SSE event/reconnect (`version`); + // `load` already closes over `path`, so it is the only callback dependency. + useEffect(() => { + load(); + }, [load, version]); + + return { data, error, loading, loaded, refetch: load }; +} + +// --------------------------------------------------------------------------- +// 4.1 Docs +// --------------------------------------------------------------------------- + +export function useDocs() { + return useApiResource('/docs'); +} + +export function useDoc(name: string | null) { + return useApiResource(name ? `/docs/${encodeURIComponent(name)}` : null); +} + +// --------------------------------------------------------------------------- +// 4.2 Skills +// --------------------------------------------------------------------------- + +export function useSkills() { + return useApiResource('/skills'); +} + +export function useSkill(slug: string | null) { + return useApiResource(slug ? `/skills/${encodeURIComponent(slug)}` : null); +} + +// --------------------------------------------------------------------------- +// 4.3 Agents +// --------------------------------------------------------------------------- + +export function useAgents() { + return useApiResource('/agents'); +} + +export function useAgent(agentType: string | null) { + return useApiResource(agentType ? `/agents/${encodeURIComponent(agentType)}` : null); +} + +// --------------------------------------------------------------------------- +// 4.4 Sessions +// --------------------------------------------------------------------------- + +export function useSessions() { + return useApiResource('/sessions'); +} + +export function useSession(sessionId: string | null) { + return useApiResource(sessionId ? `/sessions/${encodeURIComponent(sessionId)}` : null); +} + +export function useSessionTraces(sessionId: string | null) { + return useApiResource(sessionId ? `/sessions/${encodeURIComponent(sessionId)}/traces` : null); +} + +export function useSessionArtifacts(sessionId: string | null) { + return useApiResource( + sessionId ? `/sessions/${encodeURIComponent(sessionId)}/artifacts` : null + ); +} + +export function useSessionCheckpoints(sessionId: string | null) { + return useApiResource( + sessionId ? `/sessions/${encodeURIComponent(sessionId)}/checkpoints` : null + ); +} + +// --------------------------------------------------------------------------- +// 4.5 Workflow +// --------------------------------------------------------------------------- + +export function useWorkflowStatus() { + return useApiResource('/workflow/status'); +} + +export function useWorkflowGuide() { + return useApiResource('/workflow/guide'); +} + +export function useWorkflowPlans() { + return useApiResource('/workflow/plans'); +} + +export function useWorkflowPlanDetails(slug: string | null) { + return useApiResource(slug ? `/workflow/plans/${encodeURIComponent(slug)}` : null); +} + +export function useWorkflowHarness() { + return useApiResource('/workflow/harness'); +} diff --git a/web-ui/src/hooks/useEventStream.ts b/web-ui/src/hooks/useEventStream.ts new file mode 100644 index 00000000..6c5d94d8 --- /dev/null +++ b/web-ui/src/hooks/useEventStream.ts @@ -0,0 +1,123 @@ +import { useSyncExternalStore } from 'react'; +import type { RuntimeChangeEvent } from '../types/api'; + +/** + * `GET /api/events` (SSE) client. + * + * Per the contract (`web-interface-architecture.md` section 4.6), the event + * payload is intentionally coarse -- it only says *something* under + * `.context/runtime` changed, never which resource. The contract is that any + * event (including the initial `hello` and any reconnect) should cause the + * active view to refetch its REST data; nothing here should be treated as + * authoritative state. + * + * To honor that without opening one `EventSource` per consuming component, + * a single connection is shared via a module-level store (ref-counted so it + * opens lazily on first mount and closes when the last consumer unmounts). + * Every consumer (the connection badge in the layout, and every view's data + * hooks) reads the same `version` counter; bumping it is the refetch signal. + */ + +export type EventStreamStatus = 'connecting' | 'open' | 'error' | 'closed'; + +export interface EventStreamState { + status: EventStreamStatus; + /** Increments on every `hello`, every `runtime-change`, and every reconnect. */ + version: number; + lastEvent: RuntimeChangeEvent | null; + lastEventAt: string | null; +} + +const initialState: EventStreamState = { + status: 'closed', + version: 0, + lastEvent: null, + lastEventAt: null, +}; + +let state: EventStreamState = initialState; +let source: EventSource | null = null; +let refCount = 0; +const listeners = new Set<() => void>(); + +function setState(next: Partial) { + state = { ...state, ...next }; + for (const listener of listeners) listener(); +} + +function bump(partial: Partial = {}) { + setState({ ...partial, version: state.version + 1 }); +} + +function ensureConnected() { + if (source) return; + setState({ status: 'connecting' }); + const es = new EventSource(`${import.meta.env.BASE_URL.replace(/\/$/, '')}/api/events`); + source = es; + + es.addEventListener('hello', () => { + bump({ status: 'open', lastEventAt: new Date().toISOString() }); + }); + + es.addEventListener('runtime-change', (event) => { + let payload: RuntimeChangeEvent | null = null; + try { + payload = JSON.parse((event as MessageEvent).data) as RuntimeChangeEvent; + } catch { + payload = null; + } + bump({ status: 'open', lastEvent: payload, lastEventAt: new Date().toISOString() }); + }); + + // Fallback for servers/proxies that deliver unnamed `message` events. + es.onmessage = () => { + bump({ status: 'open', lastEventAt: new Date().toISOString() }); + }; + + es.onopen = () => { + setState({ status: 'open' }); + }; + + es.onerror = () => { + // EventSource auto-reconnects; treat every error as a connectivity blip + // and bump version once it reopens (see es.onopen / addEventListener + // handlers above) so consumers refetch after a reconnect too. + setState({ status: 'error' }); + }; +} + +function teardown() { + if (source) { + source.close(); + source = null; + } + state = initialState; +} + +function subscribe(listener: () => void): () => void { + refCount += 1; + ensureConnected(); + listeners.add(listener); + return () => { + listeners.delete(listener); + refCount -= 1; + if (refCount <= 0) { + refCount = 0; + teardown(); + } + }; +} + +function getSnapshot(): EventStreamState { + return state; +} + +/** + * Subscribes to the shared SSE connection. Returns the latest connection + * status plus a monotonically increasing `version` -- pass `version` as a + * dependency to any data-fetching effect to implement "refetch on any + * event" (see `useApi.ts`). + */ +export function useEventStream(): EventStreamState { + return useSyncExternalStore(subscribe, getSnapshot, () => initialState); +} diff --git a/web-ui/src/index.css b/web-ui/src/index.css new file mode 100644 index 00000000..504ff633 --- /dev/null +++ b/web-ui/src/index.css @@ -0,0 +1,119 @@ +:root { + --text: #1f2430; + --text-h: #0b0d12; + --text-muted: #667085; + --text-faint: #98a2b3; + + --bg: #f6f7f9; + --surface: #ffffff; + --surface-sunken: #f2f3f6; + --border: #e4e7ec; + --border-strong: #d0d5dd; + + --accent: #4f46e5; + --accent-hover: #4338ca; + --accent-soft: #eef0fe; + --accent-soft-border: #d7d9fb; + + --code-bg: #f4f3ec; + + --success: #16794f; + --success-soft: #e7f7ef; + --danger: #b3261e; + --danger-soft: #fdecea; + --warning: #7a4b00; + --warning-soft: #fff4e0; + + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + + --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05); + --shadow-md: 0 4px 12px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04); + + --sans: + -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, sans-serif; + --mono: ui-monospace, 'SF Mono', Consolas, monospace; + + font: 14.5px/1.55 var(--sans); + color-scheme: light; + color: var(--text); + background: var(--bg); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; +} + +#root { + min-height: 100vh; +} + +h1, +h2, +h3 { + font-family: var(--sans); + font-weight: 600; + color: var(--text-h); + letter-spacing: -0.01em; +} + +h1 { + font-size: 1.5rem; + margin: 0 0 4px; +} + +h2 { + font-size: 1.02rem; + margin: 0 0 10px; +} + +h3 { + font-size: 0.95rem; + margin: 0 0 4px; +} + +p { + margin: 0 0 8px; +} + +a { + color: var(--accent); +} + +code { + font-family: var(--mono); + font-size: 0.85em; + border-radius: 4px; + padding: 2px 5px; + background: var(--code-bg); +} + +button { + font-family: inherit; +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-thumb { + background: var(--border-strong); + border-radius: 999px; + border: 2px solid transparent; + background-clip: content-box; +} + +::-webkit-scrollbar-track { + background: transparent; +} diff --git a/web-ui/src/lib/api.ts b/web-ui/src/lib/api.ts new file mode 100644 index 00000000..56ac230e --- /dev/null +++ b/web-ui/src/lib/api.ts @@ -0,0 +1,60 @@ +/** + * Thin fetch wrapper for the `/api/*` REST contract. + * + * All routes share one envelope: `{ data }` on success, `{ error: { message } }` + * on failure (see `.context/docs/web-interface-architecture.md` section 4). + * In dev, `vite.config.ts` proxies `/api/*` to the `src/web` server; in + * production the same SPA is served by `src/web`'s static handler, so a + * relative `/api` base works in both modes. + */ + +export const API_BASE = '/api'; + +export class ApiError extends Error { + readonly status: number; + + constructor(message: string, status: number) { + super(message); + this.name = 'ApiError'; + this.status = status; + } +} + +export async function apiFetch(path: string, init?: RequestInit): Promise { + const url = path.startsWith('/api') ? path : `${API_BASE}${path}`; + const response = await fetch(url, { + ...init, + headers: { + Accept: 'application/json', + ...init?.headers, + }, + }); + + let body: unknown = null; + try { + body = await response.json(); + } catch { + // Non-JSON body (e.g. empty response); fall through to status handling below. + } + + if (!response.ok) { + const message = + body && typeof body === 'object' && body !== null && 'error' in body + ? (body as { error?: { message?: string } }).error?.message + : undefined; + throw new ApiError(message || `Request failed: ${response.status} ${response.statusText}`, response.status); + } + + if (body && typeof body === 'object' && body !== null && 'error' in body) { + const message = (body as { error?: { message?: string } }).error?.message; + throw new ApiError(message || 'Unknown API error', response.status); + } + + if (body && typeof body === 'object' && body !== null && 'data' in body) { + return (body as { data: T }).data; + } + + // Defensive fallback: some handlers could theoretically return the payload + // directly. Treat the whole body as the data in that case. + return body as T; +} diff --git a/web-ui/src/lib/markdown.ts b/web-ui/src/lib/markdown.ts new file mode 100644 index 00000000..3586b076 --- /dev/null +++ b/web-ui/src/lib/markdown.ts @@ -0,0 +1,18 @@ +export function withFrontMatter(frontMatter: Record | undefined, content: string): string { + if (!frontMatter || Object.keys(frontMatter).length === 0) { + return content; + } + + const lines = Object.entries(frontMatter).map(([key, value]) => `${key}: ${toYamlValue(value)}`); + return `---\n${lines.join('\n')}\n---\n\n${content}`; +} + +function toYamlValue(value: unknown): string { + if (Array.isArray(value)) { + return `[${value.map((item) => JSON.stringify(item)).join(', ')}]`; + } + if (typeof value === 'string') { + return /[:#{}[\],&*!|>'"%@`]/.test(value) ? JSON.stringify(value) : value; + } + return JSON.stringify(value); +} diff --git a/web-ui/src/main.tsx b/web-ui/src/main.tsx new file mode 100644 index 00000000..12c7ab1d --- /dev/null +++ b/web-ui/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import './index.css'; +import App from './App.tsx'; + +createRoot(document.getElementById('root')!).render( + + + + + +); diff --git a/web-ui/src/types/api.ts b/web-ui/src/types/api.ts new file mode 100644 index 00000000..d86fef10 --- /dev/null +++ b/web-ui/src/types/api.ts @@ -0,0 +1,424 @@ +/** + * Types for the `/api/*` contract documented in + * `.context/docs/web-interface-architecture.md` (section 4). These mirror the + * `src/harness` application-service response shapes the backend (`src/web`) + * wraps directly, kept intentionally close to that contract rather than to + * any one service's internal types -- the contract document is authoritative. + */ + +// --------------------------------------------------------------------------- +// Envelope +// --------------------------------------------------------------------------- + +export interface ApiSuccess { + data: T; +} + +export interface ApiFailure { + error: { message: string }; +} + +export type ApiEnvelope = ApiSuccess | ApiFailure; + +// --------------------------------------------------------------------------- +// 4.1 Docs +// --------------------------------------------------------------------------- + +export interface DocSummary { + name: string; + title: string; + description?: string; + category?: string; + status: 'filled' | 'unfilled'; +} + +export interface DocContent { + name: string; + frontMatter: Record; + content: string; +} + +// --------------------------------------------------------------------------- +// 4.2 Skills +// --------------------------------------------------------------------------- + +export interface SkillSummary { + slug: string; + name: string; + description: string; + phases: string[]; + isBuiltIn: boolean; + content?: string; +} + +export interface SkillsListResult { + success: boolean; + totalSkills?: number; + builtInCount: number; + customCount: number; + skills: { + builtIn: SkillSummary[]; + custom: SkillSummary[]; + }; +} + +export interface SkillContentResult { + success: boolean; + error?: string; + skill?: { + slug: string; + name?: string; + description?: string; + phases?: string[]; + isBuiltIn?: boolean; + }; + content?: string; +} + +// --------------------------------------------------------------------------- +// 4.3 Agents +// --------------------------------------------------------------------------- + +export interface AgentsDiscoverResult { + success: boolean; + totalAgents: number; + builtInCount: number; + customCount: number; + agents: { + builtIn: string[]; + custom: Array<{ type: string; path: string }>; + }; +} + +export interface AgentDocRef { + type: string; + title: string; + path: string; + description?: string; +} + +export interface AgentDetailResult { + info: { + success: boolean; + agent?: Record; + content?: string; + }; + docs: { + agent: string; + description?: string; + documentation: AgentDocRef[]; + }; +} + +// --------------------------------------------------------------------------- +// 4.4 Sessions +// --------------------------------------------------------------------------- + +export type HarnessSessionStatus = 'active' | 'paused' | 'completed' | 'failed'; +export type HarnessTraceLevel = 'debug' | 'info' | 'warn' | 'error'; +export type HarnessArtifactKind = 'text' | 'json' | 'file'; + +export interface HarnessSessionCheckpoint { + id: string; + note?: string; + data?: unknown; + artifactIds: string[]; + createdAt: string; +} + +export interface HarnessSessionRecord { + id: string; + name: string; + status: HarnessSessionStatus; + repoPath: string; + createdAt: string; + updatedAt: string; + startedAt: string; + completedAt?: string; + failedAt?: string; + lastTraceAt?: string; + lastCheckpointAt?: string; + traceCount: number; + artifactCount: number; + checkpointCount: number; + checkpoints: HarnessSessionCheckpoint[]; + metadata?: Record; +} + +export interface HarnessTraceRecord { + id: string; + sessionId: string; + level: HarnessTraceLevel; + event: string; + message: string; + createdAt: string; + data?: Record; +} + +export interface HarnessArtifactRecord { + id: string; + sessionId: string; + name: string; + kind: HarnessArtifactKind; + createdAt: string; + content?: unknown; + path?: string; + metadata?: Record; +} + +// --------------------------------------------------------------------------- +// 4.5 Workflow +// --------------------------------------------------------------------------- + +export type PrevcPhase = 'P' | 'R' | 'E' | 'V' | 'C'; +export type StatusType = 'pending' | 'in_progress' | 'completed' | 'skipped'; + +export interface OutputStatus { + path: string; + status: 'unfilled' | 'filled'; +} + +export interface PhaseStatus { + status: StatusType; + started_at?: string; + completed_at?: string; + role?: string; + current_task?: string; + reason?: string; + outputs?: OutputStatus[]; +} + +export interface AgentStatus { + status: StatusType; + started_at?: string; + completed_at?: string; + outputs?: string[]; +} + +export interface PlanApproval { + plan_created: boolean; + plan_approved: boolean; + approved_by?: string; + approved_at?: string; + approval_notes?: string; +} + +export interface ProjectMetadata { + name: string; + scale?: string; + [key: string]: unknown; +} + +export interface PrevcStatus { + project: ProjectMetadata; + phases: Record; + agents: Record; + roles?: Record; + approval?: PlanApproval; +} + +export interface WorkflowSummary { + name: string; + scale: string; + currentPhase: PrevcPhase; + progress: { + completed: number; + total: number; + percentage: number; + }; + isComplete: boolean; + startedAt: string; +} + +export interface WorkflowStatusResult { + status: PrevcStatus | null; + summary: WorkflowSummary | null; +} + +export type WorkflowGuideIntent = 'session_start' | 'pre_edit' | 'post_edit' | 'session_end' | 'explicit'; + +export interface WorkflowGuideSkillRef { + slug: string; + name: string; + description: string; + path?: string; + isBuiltIn?: boolean; +} + +export interface WorkflowGuideDecision { + allow: boolean; + block?: boolean; + reason?: string; + requires?: string[]; +} + +export interface WorkflowGuideResult { + workflow: { + active: boolean; + name?: string; + phase?: PrevcPhase; + scale?: string; + }; + context: { + initialized: boolean; + enabled?: string[]; + }; + nextSteps: string[]; + skills: WorkflowGuideSkillRef[]; + decision: WorkflowGuideDecision; + excerpt: string; +} + +export interface PlanReference { + slug: string; + path: string; + title: string; + summary?: string; + linkedAt: string; + status: 'active' | 'completed' | 'paused' | 'cancelled'; + approval_status?: 'pending' | 'approved' | 'rejected'; + approved_at?: string; + approved_by?: string; +} + +export interface WorkflowPlansResult { + success: boolean; + plans: { + active: PlanReference[]; + completed: PlanReference[]; + primary?: string; + }; +} + +export interface PlanStep { + order: number; + description: string; + assignee?: string; + deliverables?: string[]; + status: StatusType; + outputs?: string[]; + completedAt?: string; +} + +export interface PlanPhase { + id: string; + name: string; + prevcPhase: PrevcPhase; + prevcPhaseName?: string; + summary?: string; + deliverables?: string[]; + steps: PlanStep[]; + status: StatusType; + commitCheckpoint?: string; + startedAt?: string; + completedAt?: string; +} + +export interface PlanDecision { + id: string; + title: string; + description: string; + decidedBy?: string; + decidedAt?: string; + phase?: PrevcPhase; + status: 'proposed' | 'accepted' | 'rejected' | 'superseded'; + alternatives?: string[]; + consequences?: string[]; +} + +export interface LinkedPlanDetails { + ref: PlanReference; + phases: PlanPhase[]; + decisions: PlanDecision[]; + risks: unknown[]; + agents: string[]; + agentLineup: Array<{ type: string; role?: string }>; + docs: string[]; + progress: number; + currentPhase?: string; +} + +export interface WorkflowPlanDetailsResult { + success: boolean; + error?: string; + plan?: LinkedPlanDetails; +} + +export interface WorkflowHarnessBinding { + workflowName: string; + sessionId: string; + activeTaskId?: string; + createdAt: string; + updatedAt: string; +} + +export interface HarnessSensorRun { + id: string; + sensorId: string; + sessionId: string; + contractId?: string; + status: 'passed' | 'warning' | 'failed' | 'skipped' | string; + summary: string; + evidence?: string[]; + severity: 'info' | 'warning' | 'blocking' | string; + blocking: boolean; + createdAt: string; + metadata?: Record; +} + +export interface HarnessTaskContract { + id: string; + title: string; + description?: string; + sessionId?: string; + owner?: string; + status: 'draft' | 'ready' | 'in_progress' | 'blocked' | 'completed' | 'failed'; + inputs: string[]; + expectedOutputs: string[]; + acceptanceCriteria: string[]; + requiredSensors: string[]; + requiredArtifacts: unknown[]; + createdAt: string; + updatedAt: string; + metadata?: Record; +} + +export interface HarnessHandoffContract { + id: string; + from: string; + to: string; + sessionId?: string; + taskId?: string; + artifacts: string[]; + evidence: string[]; + createdAt: string; + metadata?: Record; +} + +export interface WorkflowHarnessStatus { + binding: WorkflowHarnessBinding; + session: HarnessSessionRecord; + availableSensors: Array<{ id: string; name: string; description?: string }>; + sensorRuns: HarnessSensorRun[]; + taskContracts: HarnessTaskContract[]; + handoffs: HarnessHandoffContract[]; + policyRules: number; + completionCheck: { + blocked: boolean; + reasons: string[]; + taskCompletion: { + canComplete: boolean; + missingSensors: string[]; + missingArtifacts: string[]; + blockingFindings: string[]; + } | null; + }; +} + +// --------------------------------------------------------------------------- +// 4.6 Live updates (SSE) +// --------------------------------------------------------------------------- + +export interface RuntimeChangeEvent { + paths: string[]; +} diff --git a/web-ui/src/views/AgentsView.tsx b/web-ui/src/views/AgentsView.tsx new file mode 100644 index 00000000..cc8828fd --- /dev/null +++ b/web-ui/src/views/AgentsView.tsx @@ -0,0 +1,106 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useAgent, useAgents } from '../hooks/useApi'; +import { + CopyButton, + DetailPanel, + DownloadButton, + EmptyNote, + ErrorNote, + ListPanel, + LoadingNote, + PageHeader, + TwoPaneView, +} from '../components/common'; + +export function AgentsView() { + const { data, loading, error } = useAgents(); + const [selected, setSelected] = useState(null); + const [query, setQuery] = useState(''); + + const allTypes = useMemo(() => { + if (!data) return []; + return [...data.agents.builtIn, ...data.agents.custom.map((a) => a.type)]; + }, [data]); + + useEffect(() => { + if (selected === null && allTypes.length > 0) { + setSelected(allTypes[0]); + } + }, [allTypes, selected]); + + const { data: agent, loading: agentLoading, error: agentError } = useAgent(selected); + + const filtered = useMemo(() => { + const q = query.trim().toLowerCase(); + if (!q) return allTypes; + return allTypes.filter((type) => type.toLowerCase().includes(q)); + }, [allTypes, query]); + + const content = agent?.info?.content; + + return ( + ({ + key: type, + title: type, + badge: data?.agents.builtIn.includes(type) ? 'built-in' : 'custom', + }))} + /> + } + detail={ + + {!selected && } + {selected && agentLoading && } + {selected && agentError && } + {selected && agent && ( + <> + + + + + ) + } + /> + + {agent.info?.agent && ( +
+

Info

+
{JSON.stringify(agent.info.agent, null, 2)}
+
+ )} + +
+

Documentation

+ {(!agent.docs?.documentation || agent.docs.documentation.length === 0) && ( + + )} +
    + {agent.docs?.documentation?.map((doc) => ( +
  • + {doc.title} + {doc.description && — {doc.description}} +
    {doc.path}
    +
  • + ))} +
+
+ + )} +
+ } + /> + ); +} diff --git a/web-ui/src/views/DocsView.tsx b/web-ui/src/views/DocsView.tsx new file mode 100644 index 00000000..f881245d --- /dev/null +++ b/web-ui/src/views/DocsView.tsx @@ -0,0 +1,88 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useDoc, useDocs } from '../hooks/useApi'; +import { + CopyButton, + DetailPanel, + DownloadButton, + EmptyNote, + ErrorNote, + ListPanel, + LoadingNote, + MarkdownContent, + PageHeader, + StatusPill, + TwoPaneView, +} from '../components/common'; +import { withFrontMatter } from '../lib/markdown'; + +export function DocsView() { + const { data: docs, loading, error } = useDocs(); + const [selected, setSelected] = useState(null); + const [query, setQuery] = useState(''); + + useEffect(() => { + if (selected === null && docs && docs.length > 0) { + setSelected(docs[0].name); + } + }, [docs, selected]); + + const { data: doc, loading: docLoading, error: docError } = useDoc(selected); + + const filtered = useMemo(() => { + if (!docs) return []; + const q = query.trim().toLowerCase(); + if (!q) return docs; + return docs.filter( + (d) => + (d.title || d.name).toLowerCase().includes(q) || + d.name.toLowerCase().includes(q) || + d.category?.toLowerCase().includes(q) + ); + }, [docs, query]); + + const fileContent = doc ? withFrontMatter(doc.frontMatter, doc.content) : ''; + + return ( + ({ + key: d.name, + title: d.title || d.name, + subtitle: d.category, + badge: d.status, + }))} + /> + } + detail={ + + {!selected && } + {selected && docLoading && } + {selected && docError && } + {selected && doc && ( + <> + + + + + } + /> + + + + )} + + } + /> + ); +} diff --git a/web-ui/src/views/SessionView.tsx b/web-ui/src/views/SessionView.tsx new file mode 100644 index 00000000..a3f1b722 --- /dev/null +++ b/web-ui/src/views/SessionView.tsx @@ -0,0 +1,163 @@ +import { useEffect, useMemo, useState } from 'react'; +import { + useSession, + useSessionArtifacts, + useSessionCheckpoints, + useSessionTraces, + useSessions, +} from '../hooks/useApi'; +import { + DetailPanel, + EmptyNote, + ErrorNote, + ListPanel, + LoadingNote, + PageHeader, + StatusPill, + ToolBadge, + TwoPaneView, +} from '../components/common'; + +function formatTime(iso?: string): string { + if (!iso) return '—'; + try { + return new Date(iso).toLocaleString(); + } catch { + return iso; + } +} + +function hostOf(metadata?: Record): string | undefined { + const host = metadata?.host; + return typeof host === 'string' ? host : undefined; +} + +export function SessionView() { + const { data: sessions, loading, error } = useSessions(); + const [selected, setSelected] = useState(null); + const [query, setQuery] = useState(''); + + useEffect(() => { + if (selected === null && sessions && sessions.length > 0) { + setSelected(sessions[0].id); + } + }, [sessions, selected]); + + const { data: session, loading: sessionLoading, error: sessionError } = useSession(selected); + const { data: traces, loading: tracesLoading, error: tracesError } = useSessionTraces(selected); + const { data: artifacts, loading: artifactsLoading, error: artifactsError } = useSessionArtifacts(selected); + const { data: checkpoints, loading: checkpointsLoading, error: checkpointsError } = useSessionCheckpoints(selected); + + const filtered = useMemo(() => { + const q = query.trim().toLowerCase(); + if (!q || !sessions) return sessions ?? []; + return sessions.filter((s) => (s.name || s.id).toLowerCase().includes(q) || s.id.toLowerCase().includes(q)); + }, [sessions, query]); + + return ( + ({ + key: s.id, + title: s.name || s.id, + subtitle: formatTime(s.updatedAt), + badge: s.status, + meta: , + }))} + /> + } + detail={ + + {!selected && } + {selected && sessionLoading && } + {selected && sessionError && } + {selected && session && ( + <> + + {session.name} + + } + actions={} + /> +
+
Session ID
+
{session.id}
+
Started
+
{formatTime(session.startedAt)}
+
Updated
+
{formatTime(session.updatedAt)}
+
Traces / Artifacts / Checkpoints
+
+ {session.traceCount} / {session.artifactCount} / {session.checkpointCount} +
+
+ +
+

Trace tail

+ {tracesLoading && } + {tracesError && } + {!tracesLoading && !tracesError && (!traces || traces.length === 0) && } +
    + {(traces ?? []) + .slice() + .reverse() + .map((trace) => ( +
  • + {formatTime(trace.createdAt)} + {trace.level} + {trace.event} + {trace.message} +
  • + ))} +
+
+ +
+

Artifacts

+ {artifactsLoading && } + {artifactsError && } + {!artifactsLoading && !artifactsError && (!artifacts || artifacts.length === 0) && ( + + )} +
    + {(artifacts ?? []).map((artifact) => ( +
  • + {artifact.name} ({artifact.kind}) +
    {formatTime(artifact.createdAt)}
    +
  • + ))} +
+
+ +
+

Checkpoints

+ {checkpointsLoading && } + {checkpointsError && } + {!checkpointsLoading && !checkpointsError && (!checkpoints || checkpoints.length === 0) && ( + + )} +
    + {(checkpoints ?? []).map((checkpoint) => ( +
  • + {checkpoint.note || checkpoint.id} +
    {formatTime(checkpoint.createdAt)}
    +
  • + ))} +
+
+ + )} +
+ } + /> + ); +} diff --git a/web-ui/src/views/SkillsView.tsx b/web-ui/src/views/SkillsView.tsx new file mode 100644 index 00000000..6dd2afa5 --- /dev/null +++ b/web-ui/src/views/SkillsView.tsx @@ -0,0 +1,115 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useSkill, useSkills } from '../hooks/useApi'; +import { + CopyButton, + DetailPanel, + DownloadButton, + EmptyNote, + ErrorNote, + ListPanel, + LoadingNote, + MarkdownContent, + PageHeader, + TwoPaneView, +} from '../components/common'; +import { withFrontMatter } from '../lib/markdown'; +import type { SkillSummary } from '../types/api'; + +export function SkillsView() { + const { data, loading, error } = useSkills(); + const [selected, setSelected] = useState(null); + const [query, setQuery] = useState(''); + + const allSkills = useMemo(() => { + if (!data) return []; + return [...data.skills.builtIn, ...data.skills.custom]; + }, [data]); + + useEffect(() => { + if (selected === null && allSkills.length > 0) { + setSelected(allSkills[0].slug); + } + }, [allSkills, selected]); + + const { data: skillContent, loading: contentLoading, error: contentError } = useSkill(selected); + + const filtered = useMemo(() => { + const q = query.trim().toLowerCase(); + if (!q) return allSkills; + return allSkills.filter( + (skill) => + (skill.name || skill.slug).toLowerCase().includes(q) || + skill.slug.toLowerCase().includes(q) || + skill.description?.toLowerCase().includes(q) + ); + }, [allSkills, query]); + + const fileContent = + skillContent?.skill && skillContent.content + ? withFrontMatter( + { + name: skillContent.skill.name, + description: skillContent.skill.description, + phases: skillContent.skill.phases, + }, + skillContent.content + ) + : ''; + + return ( + ({ + key: skill.slug, + title: skill.name || skill.slug, + subtitle: skill.phases?.join(', '), + badge: skill.isBuiltIn ? 'built-in' : 'custom', + }))} + /> + } + detail={ + + {!selected && } + {selected && contentLoading && } + {selected && contentError && } + {selected && skillContent && skillContent.success === false && ( + + )} + {selected && skillContent?.skill && ( + <> + + + + + ) + } + /> + {skillContent.skill.phases && skillContent.skill.phases.length > 0 && ( +
+ {skillContent.skill.phases.map((phase) => ( + + {phase} + + ))} +
+ )} + {skillContent.content && } + + )} +
+ } + /> + ); +} diff --git a/web-ui/src/views/WorkflowView.tsx b/web-ui/src/views/WorkflowView.tsx new file mode 100644 index 00000000..e0211fc5 --- /dev/null +++ b/web-ui/src/views/WorkflowView.tsx @@ -0,0 +1,176 @@ +import { useMemo } from 'react'; +import { useWorkflowHarness, useWorkflowPlanDetails, useWorkflowPlans, useWorkflowStatus } from '../hooks/useApi'; +import { EmptyNote, ErrorNote, LoadingNote, StatusPill, ToolBadge } from '../components/common'; +import type { PrevcPhase } from '../types/api'; + +const PHASE_ORDER: PrevcPhase[] = ['P', 'R', 'E', 'V', 'C']; +const PHASE_LABELS: Record = { + P: 'Planning', + R: 'Review', + E: 'Execution', + V: 'Validation', + C: 'Confirmation', +}; + +function PhaseTracker({ currentPhase, phases }: { currentPhase: PrevcPhase | null; phases: Record | null }) { + return ( +
+ {PHASE_ORDER.map((phase, index) => { + const phaseStatus = phases?.[phase]?.status ?? 'pending'; + const isCurrent = phase === currentPhase; + return ( +
+
{phase}
+
{PHASE_LABELS[phase]}
+
{phaseStatus.replace('_', ' ')}
+ {index < PHASE_ORDER.length - 1 && + ); + })} +
+ ); +} + +export function WorkflowView() { + const { data: statusResult, loading: statusLoading, error: statusError } = useWorkflowStatus(); + const { data: harness, loading: harnessLoading, error: harnessError } = useWorkflowHarness(); + const { data: plansResult } = useWorkflowPlans(); + + const planSlug = useMemo(() => { + if (!plansResult?.plans) return null; + return plansResult.plans.primary ?? plansResult.plans.active[0]?.slug ?? null; + }, [plansResult]); + + const { data: planDetails, loading: planLoading, error: planError } = useWorkflowPlanDetails(planSlug); + + const summary = statusResult?.summary ?? null; + const status = statusResult?.status ?? null; + + return ( +
+
+

Workflow

+ {statusLoading && } + {statusError && } + {!statusLoading && !statusError && !summary && } + {summary && ( + <> +

+ {summary.name} — scale: {summary.scale} — {summary.progress.completed}/{summary.progress.total} phases ( + {summary.progress.percentage}%) {summary.isComplete ? '— complete' : ''} +

+ + + )} +
+ +
+

Harness session & gates

+ {harnessLoading && } + {harnessError && } + {!harnessLoading && !harnessError && !harness && } + {harness && ( + <> +
+
Session
+
+ {harness.session.name} +
+
Tool
+
+ +
+
Gate status
+
+ + {harness.completionCheck.reasons.length > 0 && ( +
    + {harness.completionCheck.reasons.map((reason) => ( +
  • {reason}
  • + ))} +
+ )} +
+
Sensor runs
+
{harness.sensorRuns.length}
+
Task contracts
+
{harness.taskContracts.length}
+
Handoffs
+
{harness.handoffs.length}
+
+ + {harness.sensorRuns.length > 0 && ( +
    + {harness.sensorRuns.map((run) => ( +
  • + {run.sensorId} + — {run.summary} +
  • + ))} +
+ )} + + )} +
+ +
+

Agents & tools

+ {!planSlug && } + {planSlug && planLoading && } + {planSlug && planDetails?.plan && planDetails.plan.agentLineup.length === 0 && ( + + )} + {planDetails?.plan && planDetails.plan.agentLineup.length > 0 && ( +
+ {planDetails.plan.agentLineup.map((entry, index) => ( +
+
{entry.type}
+ {entry.role &&
{entry.role}
} +
+ ))} +
+ )} +
+ +
+

Linked plan

+ {!planSlug && } + {planSlug && planLoading && } + {planSlug && planError && } + {planSlug && planDetails?.success === false && } + {planDetails?.plan && ( + <> +

+ {planDetails.plan.ref.title} ({planDetails.plan.ref.slug}) +

+

Progress: {planDetails.plan.progress}%

+
    + {planDetails.plan.phases.map((phase) => ( +
  • +
    + {phase.name} + PREVC: {phase.prevcPhaseName ?? phase.prevcPhase} +
    + {phase.steps.length > 0 && ( +
      + {phase.steps.map((step) => ( +
    • + {step.description} + {step.assignee && {step.assignee}} +
    • + ))} +
    + )} +
  • + ))} +
+ + )} +
+
+ ); +} diff --git a/web-ui/tsconfig.app.json b/web-ui/tsconfig.app.json new file mode 100644 index 00000000..6830b6f7 --- /dev/null +++ b/web-ui/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/web-ui/tsconfig.json b/web-ui/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/web-ui/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/web-ui/tsconfig.node.json b/web-ui/tsconfig.node.json new file mode 100644 index 00000000..8455dcbc --- /dev/null +++ b/web-ui/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/web-ui/vite.config.ts b/web-ui/vite.config.ts new file mode 100644 index 00000000..aaf26cb3 --- /dev/null +++ b/web-ui/vite.config.ts @@ -0,0 +1,42 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +/** + * Dev-server port and proxy target for the `src/web` backend. + * + * NOTE for the backend implementer: the dotcontext web dashboard's default + * API port is **4317**. `dotcontext web` (the `src/web` server, ADR-1/ADR-2 + * in `.context/docs/web-interface-architecture.md`) should default + * `startWebServer({ port })` to 4317 so this dev proxy works out of the box. + * Override with the `VITE_API_PROXY_TARGET` env var if the backend runs on a + * different port locally. + */ +const DEFAULT_API_PROXY_TARGET = 'http://localhost:4317'; +const apiProxyTarget = process.env.VITE_API_PROXY_TARGET ?? DEFAULT_API_PROXY_TARGET; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + // Vite dev server port for the SPA itself (separate from the API port + // above). Override with `vite --port ` or `VITE_PORT` if 5173 is taken. + port: Number(process.env.VITE_PORT) || 5173, + proxy: { + '/api': { + target: apiProxyTarget, + changeOrigin: true, + // `/api/events` is a long-lived SSE connection; keep it un-buffered. + ws: false, + }, + }, + }, + preview: { + port: Number(process.env.VITE_PORT) || 5173, + proxy: { + '/api': { + target: apiProxyTarget, + changeOrigin: true, + }, + }, + }, +});