diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml new file mode 100644 index 0000000..7578daa --- /dev/null +++ b/.github/workflows/docs-deploy.yml @@ -0,0 +1,66 @@ +name: Docs Site + +on: + push: + branches: ["main"] + paths: + - "website/**" + - ".github/workflows/docs-deploy.yml" + pull_request: + branches: ["main"] + paths: + - "website/**" + - ".github/workflows/docs-deploy.yml" + workflow_dispatch: + +# Allow GITHUB_TOKEN to deploy to GitHub Pages. +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: website + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: website/package-lock.json + + - name: Install dependencies + run: npm ci + + # `next build` type-checks the project and generates .next/types, so a + # separate pre-build `tsc` step is intentionally omitted (it would run + # before those generated types exist). + - name: Build static site (Next.js export + Pagefind index) + run: npm run build + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: website/out + + deploy: + # Only publish from main; PRs run the build job above as a check. + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-latest + # Serialize production deployments only, so PR build-checks stay concurrent. + concurrency: + group: "pages-deploy" + cancel-in-progress: false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/design/designs/documentation-site.md b/design/designs/documentation-site.md new file mode 100644 index 0000000..1b12be6 --- /dev/null +++ b/design/designs/documentation-site.md @@ -0,0 +1,302 @@ +# Design: Public Documentation Site + +**Status:** Proposed (plan for a new deliverable; nothing built yet) +**Date recorded:** 2026-07-12 +**Applies to:** a new, self-contained documentation site for `loopy-loop`, to live +in this repository and be published on a `writeit.ai` subdomain. + +This document records the plan and the decisions behind it, so that when the site is +built the choices are already argued and a later reader understands *why* the stack +looks the way it does. It is written before implementation on purpose: the "what to +build" is settled here first. + +The shared principle: + +> **Ship the docs as a self-hostable static module that lives with the code, authored +> in MDX the native Next.js way, styled to read as part of the WriteIt family, and +> searchable from the keyboard.** + +--- + +## Goals + +1. **Presentable, human-first documentation.** Comprehensible to a developer meeting + `loopy-loop` for the first time — not just an API dump. Good navigation, prose, + examples. +2. **Self-hostable module.** A `clone → build → host` static site, no proprietary + docs SaaS, no external service required to run it. It ships *in this repo* so docs + version with the code. +3. **On a `writeit.ai` subdomain, styled like `writeit.ai`.** Reachable at a + subdomain pointed by CNAME, and visually in the same colour family as the main + site, so a link from `writeit.ai` feels continuous. +4. **Authored in MDX, the way Next.js's own docs work** — Markdown files that are + routes, with the option to drop in components. +5. **Keyboard search (⌘K / Ctrl+K).** A command-palette search over the docs. + +--- + +## Precedent survey (what already exists in the org) + +Three sibling frontends were reviewed before choosing an approach: + +- **`orchestra/public_and_admin_app/fe`** (the `247agents.io` site) — **the model we + will follow.** It is a Next.js 16 App-Router site whose docs are native + [`@next/mdx`](https://nextjs.org/docs/app/building-your-application/configuring/mdx) + pages: each `src/app/(public)/docs/**/page.mdx` file *is* a route. It uses Tailwind + v4 + shadcn/ui + `@tailwindcss/typography` (`prose`), `rehype-pretty-code` (Shiki) + for code highlighting, `rehype-slug` for heading anchors, a hand-maintained + navigation array (`src/lib/docs/navigation.ts`), a docs `layout.tsx` giving a + three-column sidebar / prose / on-this-page shell with prev–next pagination, and it + statically exports (`output: 'export'`) to a static host. It was assessed as + "highly self-contained and very easy to lift" into a standalone docs site. It has + **no search** and its dark-mode tokens exist but are not wired to a toggle. + +- **`writeit/fe`** (the `writeit.ai` marketing site) — **the source of the palette and + the hosting reference, not the docs engine.** Next.js 15 static export on Firebase + Hosting. Its docs engine is a hand-rolled `next-mdx-remote` system whose route was + actually deleted (commit `1db91162`); we do **not** reuse it. What we take from here + is the brand colour palette (below) and the confirmation that WriteIt ships static + Next.js exports behind per-subdomain hosting. + +- There is **no `composer` repo** in `moje/` (an earlier assumption); the real + precedent is orchestra, above. + +--- + +## Decision 1 — Replicate orchestra's native `@next/mdx` pattern + +### Decision + +Build the site by replicating orchestra's docs system: **native `@next/mdx` with +`page.mdx`-as-route**, Tailwind v4 + shadcn/ui + `@tailwindcss/typography`, +`remark-gfm` + `rehype-slug` + `rehype-pretty-code`, a hand-maintained navigation +array, a docs layout with sidebar + on-this-page TOC + prev/next, and +`output: 'export'` for a fully static build. + +### Context / why + +It is a proven in-house pattern, it is exactly the "Next.js docs authored in MDX" +feel that was asked for (MDX files *are* the pages), and it is genuinely +self-contained — roughly six files to model. Staying on the org's existing convention +means no new framework to learn or maintain. + +### Alternatives considered and rejected + +- **Fumadocs** (a purpose-built MDX docs framework). It ships ⌘K search and dark mode + for free, which is attractive. Rejected as the default because it is a new framework + outside the org's convention and adds surface area; the one thing it would save us — + search — we can add with a small, self-hostable dependency (Decision 4). *This + remains the fallback if we later decide the search wiring is not worth owning.* +- **Reuse `writeit/fe`'s `next-mdx-remote` engine.** Rejected: older approach, no + Tailwind, no search, and its route was deleted — orchestra's system is strictly the + better internal precedent. + +--- + +## Decision 2 — Live inside this repo, under `website/` + +### Decision + +The site lives in the `loopy-loop` repository at `website/`, not in a separate repo +and not inside the `writeit` monorepo. + +### Context / why + +Goal 2 (self-hostable module) and the choice of GitHub Pages (Decision 3) both point +here: the docs version alongside the code they describe, in one place a contributor +can build and host. Putting them in the private `writeit` monorepo would couple an +Apache-2.0 OSS project's docs to a private marketing repo and undercut "self-hostable +module"; a separate docs repo would let docs drift from code. + +### Consequences + +- Docs PRs and code PRs can move together; a change that alters the CLI can update its + page in the same change. +- The `website/` app has its own `package.json` / toolchain, independent of the Python + package. It is not published to PyPI; it is only ever built into a static site. + +--- + +## Decision 3 — Host on GitHub Pages at `loopy.writeit.ai` via CNAME + +### Decision + +Publish the static export to **GitHub Pages** from this repo, served at the custom +subdomain **`loopy.writeit.ai`** pointed via CNAME. + +### Context / why + +GitHub Pages keeps hosting with the OSS repo (no separate cloud project), supports a +custom domain, and needs no server since the site is a static export. The +product-branded subdomain (`loopy.writeit.ai`) was chosen over a generic +`docs.writeit.ai` so the URL ties to the tool by name. + +### Mechanics + +- **Build/deploy:** a GitHub Actions workflow builds the static export, runs the + search indexer (Decision 4) over the output, and deploys with `actions/deploy-pages`. + A `.nojekyll` marker is included so Next's `_next/` assets are served. +- **Next.js config:** `output: 'export'` plus `trailingSlash: true` so directory-style + URLs resolve to `index.html` on Pages. Because the site is served at the subdomain + **root**, no `basePath` is needed. +- **Custom domain:** a one-time provisioning step, since an Actions-based Pages + deploy does not read a `CNAME` file from the artifact. In the repository's + **Settings → Pages**, set the source to **GitHub Actions** and the custom domain to + `loopy.writeit.ai`; add a DNS record in the `writeit.ai` zone + (`loopy.writeit.ai CNAME writeitai.github.io`); then enable Enforce HTTPS. The + committed `public/CNAME` records the intended domain but does not bind it on its own. + Until the domain is bound, the site is served under + `writeitai.github.io/loopy-loop/`, where root-relative `/_next/` and `/pagefind/` + URLs would not resolve — so this step precedes sharing the link. See + `website/README.md` for the exact checklist. + +### Alternatives considered and rejected + +- **Firebase Hosting** (what `writeit` and `orchestra` use). It would match org ops + most closely and CNAME the same way, but it pulls the OSS project into a private GCP + project. Rejected in favour of keeping hosting self-contained with the repo. (If ops + consistency later matters more than repo-locality, this is the natural switch and + requires no code change — only a different deploy step.) +- **Vercel.** Best Next.js DX, but a new platform in the stack for no gain over static + Pages here. + +--- + +## Decision 4 — Keyboard (⌘K) search via Pagefind + `cmdk` + +### Decision + +Add search with **[Pagefind](https://pagefind.app/)** (a static, self-hostable search +that indexes the built HTML as a post-build step) surfaced through a **shadcn +`Command` dialog (`cmdk`)** bound to ⌘K / Ctrl+K. + +### Context / why + +GitHub Pages is static-only, and the site must stay a self-hostable module — so an +external search service (Algolia DocSearch and friends) is out. Pagefind builds its +index from the exported HTML with no backend and a tiny client runtime, and `cmdk` +(already the basis of shadcn's `Command` component) gives the classic command-palette +UX. This closes orchestra's one real gap (it has no search) without leaving the +self-hostable constraint. + +### Consequences + +- Search index is generated at build time and served as static assets; no query-time + server. +- The palette is a small client component mounted globally and opened by a keyboard + listener. + +--- + +## Decision 5 — WriteIt palette, open-font substitute, light-first + +### Decision + +Style the site with `writeit.ai`'s brand colours, substitute an **open font** for the +domain-locked proxima-nova, and ship **light mode first** (dark mode as a fast-follow). + +### The palette (from `writeit/fe/styles/globals.scss`) + +| Token | Hex | Role | +|---|---|---| +| sand | `#f7ebbd` | page background | +| ink | `#222433` | text / headings | +| green | `#5ca493` | primary accent | +| gold | `#ebaa1a` | secondary accent | +| red | `#f34832` | alert / emphasis | + +These become the shadcn/Tailwind CSS variables (`--background`, `--foreground`, +`--primary`, etc.), replacing orchestra's monochrome neutral tokens. + +### Context / why + +- **Font:** `writeit.ai` uses **proxima-nova**, a paid Adobe Typekit font served from + a domain-locked kit. It cannot ship in a self-hostable OSS module and will not render + off `writeit.ai` domains. We therefore match the *colours* exactly and pick a close + open substitute — **Hanken Grotesk** or **Figtree** (both load via `next/font`, + self-host cleanly, and are visually adjacent to proxima-nova). This keeps the module + truly self-hostable while preserving brand feel. +- **Light-first:** `writeit.ai` itself is light-only, so light mode is the faithful + match. Dark mode is a cheap fast-follow (`next-themes` + the shadcn dark tokens + orchestra already defines) and is deferred, not designed out. + +--- + +## Decision 6 — Restructure existing content into MDX; reconcile `SKILL.md` + +### Decision + +The first version restructures **existing** documentation into MDX pages rather than +writing net-new prose, and reconciles the drifted `skills/loopy-loop/SKILL.md` against +the current API as part of that pass. + +### Context / why + +The content is roughly 80% already written — the 466-line `README.md`, +`docs/session-layout.md`, `docs/http-contract.md`, the accepted +`success-semantics-and-evaluation.md` design doc, the `CHANGELOG`, and `SKILL.md`. +This is primarily a re-homing and structuring job. `SKILL.md` currently references the +older `.loopy_loop/workflows/` layout and pre-0.2.0 polling language; the docs must +follow the README's current two-endpoint / `workflow_sets/` model, and the drift is +worth fixing while the content is being touched. + +### Proposed information architecture + +Each entry is one `page.mdx`, sourced from the material in parentheses. + +| Route | Source material | +|---|---| +| `/docs` — Introduction | README overview, tagline, value prop | +| `/docs/getting-started` | README install (`uv`/`pip`), Agent Skill, `loopy init`, first run | +| `/docs/concepts` | Architecture: coordinator/worker, team-harness delegation, the iteration loop, session dir as durable state | +| `/docs/configuration` | `loopy_loop_config.yaml`, `team_harness_*` settings | +| `/docs/workflows` | Workflow sets, scheduling fields, cadence, reserved `goal_check`, the three templates | +| `/docs/success-and-control` | `control.json` vs `goal_check.json`, `stop_reason` values (from the success-semantics design doc) | +| `/docs/evaluation` | LLM-as-judge / eval-banana; why agent-authored deterministic checks are forbidden | +| `/docs/session-layout` | `docs/session-layout.md` | +| `/docs/http-contract` | `docs/http-contract.md` | +| `/docs/child-sessions` | `child_requests` contract, `pm_planner_dispatcher` | +| `/docs/cli-reference` | `init` / `coordinator` / `worker` / `status` / `stop` | +| `/docs/troubleshooting` | `SKILL.md` "Common Pitfalls" | + +The nav order and grouping live in one hand-maintained array (orchestra's +`navigation.ts` pattern), which also drives prev/next. + +--- + +## Stack summary + +`Next.js` (App Router) · `@next/mdx` · `output: 'export'` + `trailingSlash: true` · +`Tailwind v4` + `shadcn/ui` + `@tailwindcss/typography` · `remark-gfm` + +`rehype-slug` + `rehype-pretty-code` (Shiki) · `Pagefind` + `cmdk` for ⌘K search · +WriteIt palette with Hanken Grotesk / Figtree · deployed to GitHub Pages at +`loopy.writeit.ai`. + +--- + +## Follow-up (out of scope for this repo's PR) + +After the site is live, add a "Docs" link on `writeit.ai` pointing to +`https://loopy.writeit.ai`. That is a separate change in the `writeit` repo and is +tracked as a follow-up, not part of building the site here. + +--- + +## Summary + +| Decision | Choice | Why | +|---|---|---| +| 1. Engine | Replicate orchestra's native `@next/mdx` pattern | Proven in-house, MDX-as-routes, self-contained; not a new framework | +| 2. Location | `website/` in this repo | Self-hostable module; docs version with code | +| 3. Hosting | GitHub Pages at `loopy.writeit.ai` via CNAME | Hosting stays with the OSS repo; static, no server | +| 4. Search | Pagefind + `cmdk` (⌘K) | Static, self-hostable, closes orchestra's search gap | +| 5. Style | WriteIt palette + open font, light-first | Brand-continuous; font stays self-hostable; faithful to a light-only site | +| 6. Content | Restructure existing docs into MDX; fix `SKILL.md` drift | Content ~80% written; keep docs on the current API | + +### When to revisit + +- If owning the search wiring proves not worth it, switch Decision 1 to **Fumadocs** + (search + dark mode built in). The content (MDX) ports with little change. +- If org ops-consistency outweighs repo-locality, switch Decision 3 to **Firebase + Hosting** (same CNAME flow, different deploy step, no code change). +- Add **dark mode** (`next-themes` + existing shadcn dark tokens) once light mode ships. diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..d79b13c --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,16 @@ +{ + "version": 1, + "skills": { + "eval-banana": { + "source": "git@github.com:writeitai/eval-banana.git", + "sourceType": "git", + "skillPath": "skills/eval-banana/SKILL.md", + "computedHash": "8fa222f3ed19bb622bf6b339b5fc8528be7817d70ed4cf471a7ea4ba22064e4c" + }, + "skill-creator": { + "source": "anthropics/skills", + "sourceType": "github", + "computedHash": "5ea13a6d9f0d4bb694405d79acd00cadec0d21bb138c4dd10fcf3c500cb835c2" + } + } +} diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..7f7fdd5 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,25 @@ +# dependencies +/node_modules + +# next.js +/.next/ +/out/ +next-env.d.ts + +# production build artifacts +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files +.env*.local + +# typescript +*.tsbuildinfo diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..05facba --- /dev/null +++ b/website/README.md @@ -0,0 +1,71 @@ +# loopy-loop docs site + +The public documentation for [loopy-loop](https://github.com/writeitai/loopy-loop), +published at **[loopy.writeit.ai](https://loopy.writeit.ai)**. + +It is a self-contained static site: a Next.js App-Router app whose pages are authored +as MDX, exported to static HTML, and served by GitHub Pages. See +[`design/designs/documentation-site.md`](../design/designs/documentation-site.md) for +the decisions behind the stack. + +## Stack + +- **Next.js** (App Router) with **`@next/mdx`** — each `src/app/docs/**/page.mdx` file + is a route. +- **Tailwind v4** + **`@tailwindcss/typography`** for prose, themed to the WriteIt + brand palette in `src/app/globals.css`. +- **`rehype-pretty-code`** (Shiki) code highlighting, **`rehype-slug`** heading + anchors, **`remark-gfm`**. +- **Pagefind** static search surfaced through a **`cmdk`** ⌘K command palette. +- `output: 'export'` → fully static; no server. + +## Develop + +```bash +cd website +npm install +npm run dev # http://localhost:3000 +``` + +> Search (⌘K) only returns results in a production build — the Pagefind index is +> generated by `postbuild`, so it is absent during `npm run dev`. + +## Build + +```bash +npm run build # next build (-> out/) then pagefind indexes out/ +``` + +The static site is written to `website/out/`. Preview that production build (with +a working search index) via `npm run preview`. `npm run typecheck` runs `tsc +--noEmit` — run it after a build, since it depends on Next's generated types. + +## Add or edit a page + +1. Create `src/app/docs//page.mdx`. Start it with an exported `metadata` + object (`title`, `description`), then a single `#` H1, then content. Use `##`/`###` + headings — they populate the "On this page" table of contents. +2. Add the page to `src/lib/docs/navigation.ts`. That array is the single source of + truth for the sidebar order and prev/next pagination. + +## Deploy + +`.github/workflows/docs-deploy.yml` builds and deploys to GitHub Pages on every push +to `main` that touches `website/**` (pull requests run the build as a check only). +`public/.nojekyll` keeps GitHub Pages from stripping Next's `_next/` assets. + +### One-time setup (required before the first deploy) + +The workflow publishes the artifact, but the Pages site and its custom domain must be +provisioned once in the repository — the `public/CNAME` file does **not** configure the +domain on its own for an Actions-based deployment: + +1. **Settings → Pages → Build and deployment → Source:** select **GitHub Actions**. +2. **Settings → Pages → Custom domain:** enter `loopy.writeit.ai` and save (this is + what actually binds the domain; the committed `CNAME` file just records the intent). +3. **DNS** (in the `writeit.ai` zone): add `loopy.writeit.ai CNAME writeitai.github.io.` +4. Once DNS resolves, enable **Enforce HTTPS** in Settings → Pages. + +Until the custom domain is bound, the site would be served under +`https://writeitai.github.io/loopy-loop/`, where the root-relative `/_next/` and +`/pagefind/` URLs do not resolve — so complete the steps above before sharing the link. diff --git a/website/mdx-components.tsx b/website/mdx-components.tsx new file mode 100644 index 0000000..2bad6d1 --- /dev/null +++ b/website/mdx-components.tsx @@ -0,0 +1,36 @@ +import type { MDXComponents } from "mdx/types"; +import type { AnchorHTMLAttributes } from "react"; +import Link from "next/link"; + +// Route internal links through next/link so they navigate client-side and pick +// up `trailingSlash: true` (avoiding a GitHub Pages 301 on every doc link). +// External links open in a new tab; in-page anchors stay plain . +function MdxAnchor({ + href = "", + children, + ...props +}: AnchorHTMLAttributes) { + if (href.startsWith("/")) { + return ( + + {children} + + ); + } + const external = /^https?:\/\//.test(href); + return ( + + {children} + + ); +} + +// Required by @next/mdx. Global MDX element styling is handled by the +// `prose` classes on the docs
. +export function useMDXComponents(components: MDXComponents): MDXComponents { + return { ...components, a: MdxAnchor }; +} diff --git a/website/next.config.ts b/website/next.config.ts new file mode 100644 index 0000000..6dc750e --- /dev/null +++ b/website/next.config.ts @@ -0,0 +1,26 @@ +import type { NextConfig } from "next"; +import createMDX from "@next/mdx"; + +const nextConfig: NextConfig = { + reactStrictMode: true, + // Fully static site — exported to `out/` and served by GitHub Pages. + output: "export", + // Directory-style URLs (`/docs/getting-started/`) resolve to index.html on a + // static host that does not rewrite clean URLs (GitHub Pages). + trailingSlash: true, + // Let `page.mdx` files be routes, the way the Next.js docs are authored. + pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"], + images: { unoptimized: true }, +}; + +const withMDX = createMDX({ + options: { + remarkPlugins: ["remark-gfm"], + rehypePlugins: [ + "rehype-slug", + ["rehype-pretty-code", { theme: "github-light", keepBackground: false }], + ], + }, +}); + +export default withMDX(nextConfig); diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..79d4385 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,4779 @@ +{ + "name": "loopy-loop-docs", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "loopy-loop-docs", + "version": "0.1.0", + "dependencies": { + "@mdx-js/loader": "^3.1.1", + "@mdx-js/react": "^3.1.1", + "@next/mdx": "^16.0.0", + "@radix-ui/react-dialog": "^1.1.1", + "@types/mdx": "^2.0.13", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lucide-react": "^0.462.0", + "next": "^16.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "rehype-pretty-code": "^0.14.3", + "rehype-slug": "^6.0.0", + "remark-gfm": "^4.0.1", + "shiki": "^4.0.2", + "tailwind-merge": "^3.0.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.0.0", + "@tailwindcss/typography": "^0.5.19", + "@types/node": "^22.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "pagefind": "^1.3.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.5.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mdx-js/loader": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/loader/-/loader-3.1.1.tgz", + "integrity": "sha512-0TTacJyZ9mDmY+VefuthVshaNIyCGZHJG2fMnGaDttCt8HmjUF7SizlHJpaCDoGnN635nK1wpzfpx/Xx5S4WnQ==", + "license": "MIT", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "webpack": ">=5" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "license": "MIT", + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@next/env": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.10.tgz", + "integrity": "sha512-zLPxg9M0MEHmygpj5OuxjQ+vHMiy/K7cSp74G8ecYolmgUWw0RwN02tF56npup/+qaI8JB97hQgS/r2Hb6QwVA==", + "license": "MIT" + }, + "node_modules/@next/mdx": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/mdx/-/mdx-16.2.10.tgz", + "integrity": "sha512-r6T32AyQ0xy6p0vKd1lNbz6RUXuVXdGYSAI0dRrpbnqGnTWQXefADZGui4PlwjqROj0XQBMqVwot9ntPWao9PA==", + "license": "MIT", + "dependencies": { + "source-map": "^0.7.0" + }, + "peerDependencies": { + "@mdx-js/loader": ">=0.15.0", + "@mdx-js/react": ">=0.15.0" + }, + "peerDependenciesMeta": { + "@mdx-js/loader": { + "optional": true + }, + "@mdx-js/react": { + "optional": true + } + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.10.tgz", + "integrity": "sha512-v9IdJCa0H0mbo+8z5zwUpOk1Vj7RjkcI5uNYf5Ws1y6szf/p3Mzl9hLaST8SCt6L9h8NGnruZcd2+o0NTNwDhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.10.tgz", + "integrity": "sha512-17IS0jJRViROGmA9uGdNR8VPJpfbnaVG7E9qhso5jDLkmyd0lSDORWxbcKINzcFqzZqGwGtMSnrFRxBpuUYjLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.10.tgz", + "integrity": "sha512-GRQRsRtuciNJvB54AvvuQTiq0oZtFwa1owQqtZD8wwnGpM2L39MV22kpI72YSXLKIyY40LC66EiLFv4PiicXxg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.10.tgz", + "integrity": "sha512-zkN9MQYS7UQBro+FnISUq1itaQjXI9xqISzuQ+2bc921NcJ1x4yPCqrn77tVN6/dOOXaaWVX3k6/bR07pPwK+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.10.tgz", + "integrity": "sha512-iCVJnwvrPYECvA6WM/7+oo+OiTvedIKLxtCLAZP4xZR3nXa1zmzZyLPbYCmWvpd4CvMYF1EMTafd0ii3DygLvA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.10.tgz", + "integrity": "sha512-ov2g4H0dHY9bPoOU83m91hWT7Iq5qy13bUnyyshLU3HGR1Ownn0X9QpmDPc5iIUaahTp7f7LeGAhV4DSFtackw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.10.tgz", + "integrity": "sha512-DwAnhLX76HQiFFQNgWlcK+JzlnD1rZ+UK/WY0ZMI/deXpvgnesjNYrqcfo1JzBuz4Kf7o3brIBL0glI1junatA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.10.tgz", + "integrity": "sha512-0JXq3b85Jk9Jg4ntLUbXSPvoDw3gpZou7twuKdoFG2jOw635v7+IiXfTaa0TxVMyx78pUjnrVYwLgjKfX4e6/A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", + "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", + "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.19.tgz", + "integrity": "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.12", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", + "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-effect-event": "0.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", + "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", + "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", + "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", + "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", + "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", + "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@shikijs/core": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.3.1.tgz", + "integrity": "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.3.1", + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.3.1.tgz", + "integrity": "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.3.1.tgz", + "integrity": "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.3.1.tgz", + "integrity": "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.3.1.tgz", + "integrity": "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.3.1.tgz", + "integrity": "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.3.1.tgz", + "integrity": "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", + "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", + "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-x64": "4.3.2", + "@tailwindcss/oxide-freebsd-x64": "4.3.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-x64-musl": "4.3.2", + "@tailwindcss/oxide-wasm32-wasi": "4.3.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", + "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", + "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", + "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", + "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", + "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", + "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", + "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", + "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", + "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", + "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", + "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", + "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.2.tgz", + "integrity": "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.2", + "@tailwindcss/oxide": "4.3.2", + "postcss": "^8.5.15", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.20.tgz", + "integrity": "sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >=4.0.0 || insiders" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "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/mdx": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", + "license": "MIT" + }, + "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": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.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==", + "devOptional": 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.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "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/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/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/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "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/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "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/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "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/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.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", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.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/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "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/lucide-react": { + "version": "0.462.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.462.0.tgz", + "integrity": "sha512-NTL7EbAao9IFtuSivSZgrAh4fZd09Lr+6MTkqIxuHaH2nnYiYIzXPo06cOxHg9wKLdj6LL8TByG4qpePqwgx/g==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^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-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.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-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.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-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.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-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "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.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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/next": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.10.tgz", + "integrity": "sha512-2som5AVXb3kE6Yjine3/mNbBayYF58eguBWIVVUdr1y/L426xyVEgYxgBG+1QC34P2x5E+tcDup6XkuOAX3dCA==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.10", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.10", + "@next/swc-darwin-x64": "16.2.10", + "@next/swc-linux-arm64-gnu": "16.2.10", + "@next/swc-linux-arm64-musl": "16.2.10", + "@next/swc-linux-x64-gnu": "16.2.10", + "@next/swc-linux-x64-musl": "16.2.10", + "@next/swc-win32-arm64-msvc": "16.2.10", + "@next/swc-win32-x64-msvc": "16.2.10", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", + "dev": true, + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" + } + }, + "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/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "license": "ISC" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.18.tgz", + "integrity": "sha512-xdB1oSLHbz1vRWgCDalrCqEFTWzFlhqFC5tIHLMOSUIjhm3XXQ1qrFy8S/ESr1JYRRXqM3c1QFiMZUJdUTqyMQ==", + "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/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "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-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-pretty-code": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.14.4.tgz", + "integrity": "sha512-FKPkiSnTqtJHKjCwfqfQs0tXSkmcq7K3jsryZI0rpazo2kWgSYw+sk18GAhxDzKdPk004WWWZ5yiZFEUQhN4Bg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "hast-util-to-string": "^3.0.0", + "parse-numeric-range": "^1.3.0", + "rehype-parse": "^9.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "shiki": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shiki": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.3.1.tgz", + "integrity": "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.3.1", + "@shikijs/engine-javascript": "4.3.1", + "@shikijs/engine-oniguruma": "4.3.1", + "@shikijs/langs": "4.3.1", + "@shikijs/themes": "4.3.1", + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", + "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "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/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "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-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.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/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/website/package.json b/website/package.json new file mode 100644 index 0000000..439ea4d --- /dev/null +++ b/website/package.json @@ -0,0 +1,41 @@ +{ + "name": "loopy-loop-docs", + "version": "0.1.0", + "private": true, + "description": "Documentation site for loopy-loop, published at loopy.writeit.ai", + "scripts": { + "dev": "next dev", + "build": "next build", + "postbuild": "pagefind --site out", + "preview": "npx --yes serve out", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@mdx-js/loader": "^3.1.1", + "@mdx-js/react": "^3.1.1", + "@next/mdx": "^16.0.0", + "@radix-ui/react-dialog": "^1.1.1", + "@types/mdx": "^2.0.13", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lucide-react": "^0.462.0", + "next": "^16.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "rehype-pretty-code": "^0.14.3", + "rehype-slug": "^6.0.0", + "remark-gfm": "^4.0.1", + "shiki": "^4.0.2", + "tailwind-merge": "^3.0.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.0.0", + "@tailwindcss/typography": "^0.5.19", + "@types/node": "^22.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "pagefind": "^1.3.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.5.3" + } +} diff --git a/website/postcss.config.mjs b/website/postcss.config.mjs new file mode 100644 index 0000000..79bcf13 --- /dev/null +++ b/website/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/website/public/.nojekyll b/website/public/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/website/public/CNAME b/website/public/CNAME new file mode 100644 index 0000000..d7a3fb9 --- /dev/null +++ b/website/public/CNAME @@ -0,0 +1 @@ +loopy.writeit.ai diff --git a/website/src/app/docs/child-sessions/page.mdx b/website/src/app/docs/child-sessions/page.mdx new file mode 100644 index 0000000..df66a09 --- /dev/null +++ b/website/src/app/docs/child-sessions/page.mdx @@ -0,0 +1,135 @@ +export const metadata = { + title: "Child Sessions", + description: "How a workflow spawns a sequential child session via child_requests, how depth-first single-child execution works, and the pm_planner_dispatcher example.", +}; + +# Child Sessions + +Sometimes a session needs to hand a self-contained piece of work to its own +dedicated loop — with its own goal, its own workflow set, and its own session +directory — and then pick up where it left off once that work is done. That is what +a child session is. A workflow requests one by dropping a JSON file into the +session's `child_requests/` directory; the coordinator runs the child loop to a +terminal state and then resumes the parent. This page describes that contract and +walks through the packaged `pm_planner_dispatcher` set that uses it. + +## The child_requests contract + +A workflow requests a child session by writing a JSON file into the active +session's `child_requests/` directory. The request names a workflow set and a goal: + +```json +{ + "workflow_set": "inner_outer_eval", + "goal": "A complete, self-contained implementation goal for the selected item.", + "schema_version": 1 +} +``` + +When the coordinator sees a valid request (after a successful iteration), it: + +1. Derives a goal hash and creates a new child `session_id`. +2. Creates the child session directory **under the parent's `children/` + directory**, and copies the request `goal` into the child's `goal.md`. +3. Records the child in the parent's `children.json` ledger with status + `running`. +4. Removes the request file and dispatches the child's first workflow. +5. When the child reaches a terminal state, marks the child record complete (with + its final status and `stop_reason`) and resumes the parent session. + +The `goal` in the request becomes the child's source-of-truth goal, exactly as if +that child had been started on its own. The child runs its named `workflow_set` +independently — different workflows, different state, different eval checks. + +## Depth-first, one child at a time + +v1 child execution is deliberately simple: **depth-first, single-child-at-a-time.** + +- A request is only picked up **after a successful iteration** in the parent. The + coordinator does not interrupt an in-flight task to start a child. +- Requests are processed one at a time, in sorted filename order. Exactly one child + is dispatched per opportunity. +- While a child runs, the parent is suspended. The parent resumes only after the + child reaches a terminal state (`goal_met`, `unresolvable_error`, `max_turns`, + and so on — see [Success & Control](/docs/success-and-control) for the full set). +- **Only a top-level session can spawn children.** A child session cannot itself + request a grandchild in v1; child requests are ignored inside a session that + already has a parent. This keeps the tree one level deep and the execution order + easy to reason about. + +The parent tracks its children in a `children.json` ledger at the session root, and +the child directories themselves live under `children/`: + +```text +.loopy_loop/sessions// +├── children.json # ledger: one record per child +├── child_requests/ # inbox for pending child requests +└── children/ + └── / # a full session directory of its own + ├── goal.md + ├── project_state/ + ├── iterations/ + └── ... +``` + +Each `children.json` record captures the child `session_id`, `workflow_set`, +`goal_hash`, and `status`, plus a `completed_at` and `stop_reason` once the child +finishes. That gives the parent a durable, inspectable index of every child it +started and how each one ended. + +## Worked example: pm_planner_dispatcher + +The packaged `pm_planner_dispatcher` workflow set is built entirely around this +contract. Scaffold it with: + +```bash +loopy init --template pm_planner_dispatcher +``` + +It turns the parent session into a project-management loop that never implements +product code directly. Instead it plans work and delegates each concrete item to a +child implementation session (typically running the `inner_outer_eval` set). Two +workflows split the job: + +### planner + +`planner` runs on start and owns the PM state and the stop decision. Each run it: + +- Treats the session goal as the source of truth and maintains PM state under + `project_state/` (`work_items.md`, `current_state.md`, `decisions.md`, + `child_sessions.md`, and the outer-owned `finished.md`). +- Reflects any `updates_from_user.md` content into that state, then clears the + file. +- Selects exactly **one** available work item for the dispatcher to hand off. +- Reviews terminal child-session evidence and marks the item `accepted` or + `needs_rework`. +- Stops the loop only when the **full** session goal is satisfied — not merely + because one item was accepted — by writing `goal_met` (or `unresolvable_error`) + to `control.json`. + +The planner never writes child request files, and never implements child work +itself. + +### dispatcher + +`dispatcher` must follow the planner and owns the `child_requests/` directory. Each +run it does exactly one of: + +- Writes **one** child request JSON file for the selected item, with a complete, + self-contained implementation goal (item id/title, acceptance criteria, + constraints, expected delivery evidence), then marks the item + `waiting_for_child`. +- Imports terminal child-session evidence — links to the child session path, + `finished.md`, eval or `goal_check` results, PR/merge status — back into PM state + and marks the item `ready_for_review`. + +The dispatcher never makes the acceptance decision (planner owns that), never stops +the loop, and creates at most one child request per iteration. + +Together, `planner → dispatcher` forms the parent loop, and each dispatched child +is a full `inner_outer_eval` session that implements and evaluates one item before +control returns to the planner. + +For the directory contract these sessions share, see +[Session Layout](/docs/session-layout). For how workflow sets, scheduling, and +`must_follow` ordering work, see [Workflows](/docs/workflows). diff --git a/website/src/app/docs/cli-reference/page.mdx b/website/src/app/docs/cli-reference/page.mdx new file mode 100644 index 0000000..746ac94 --- /dev/null +++ b/website/src/app/docs/cli-reference/page.mdx @@ -0,0 +1,104 @@ +export const metadata = { + title: "CLI Reference", + description: + "Every loopy-loop command: init, coordinator, worker, status, and stop, with their flags and usage examples.", +}; + +# CLI Reference + +loopy-loop is driven by a single command-line tool with five subcommands: `init` scaffolds a repo, `coordinator` and `worker` run the loop, and `status` and `stop` control it. Every command operates on the current working directory, so run them from the root of your target repository. + +## Command aliases + +The package installs two entry points that behave identically: `loopy` and `loopy-loop`. This reference uses `loopy`; use whichever reads better in your shell. + +```bash +loopy status +loopy-loop status # exactly the same command +``` + +## loopy init + +Scaffolds loopy-loop's files into the current repository. It is idempotent: it creates any missing files, leaves existing files untouched, and always ensures `.loopy_loop/sessions/` is in `.gitignore`. When it finishes, it prints the list of files it created (or reports that the repo is already initialized). + +| Option | Default | Description | +| --- | --- | --- | +| `--template` | `default` | Which workflow template to scaffold. One of `default`, `inner_outer_eval`, or `pm_planner_dispatcher`. | + +The `default` template creates only the reserved `goal_check` workflow in a set named `main`. `inner_outer_eval` creates the recommended `outer`/`inner`/`eval_reviewer`/`eval_runner` set. `pm_planner_dispatcher` creates `planner`/`dispatcher` workflows for child-session orchestration. See [Workflows](/docs/workflows) for how to choose. + +```bash +loopy init --template inner_outer_eval +``` + +## loopy coordinator + +Runs the coordinator server — the process that owns loop state and dispatches work. It loads `loopy_loop_config.yaml`, runs a startup preflight, resolves the goal, creates (or resumes) a session, and serves exactly two endpoints, `/register` and `/finished`. Leave it running while the loop is active. + +| Option | Default | Description | +| --- | --- | --- | +| `--host` | `0.0.0.0` | Interface to bind. Use `127.0.0.1` to keep it local. | +| `--port` | `8080` | Port to listen on. | +| `--resume` | off | Reattach to a non-terminal existing session instead of starting fresh. Required when a previous coordinator was killed without reaching a terminal state. | +| `--workflow-set` | from config | Run this workflow set for the new session instead of the `workflow_set` in `loopy_loop_config.yaml`. | +| `--goal-file` | from config | Copy this goal file into the new session as `goal.md`, overriding the configured `goal_file`. | + +```bash +loopy coordinator --host 127.0.0.1 --port 8080 +``` + +Reattach to a session whose coordinator was killed: + +```bash +loopy coordinator --host 127.0.0.1 --port 8080 --resume +``` + +Starting a coordinator against a still-running session without `--resume` is intentionally fatal, so two coordinators never share one state file. See the [HTTP Contract](/docs/http-contract) for the endpoint payloads. + +## loopy worker + +Runs a blocking worker that executes assignments through `team-harness`. It calls `/register` once for its first task, then loops — running each workflow and reporting via `/finished` — until the coordinator returns a stop response. You can run several workers against one coordinator. + +| Option | Default | Description | +| --- | --- | --- | +| `--coordinator` | required | Base URL of the coordinator, for example `http://127.0.0.1:8080`. | + +```bash +loopy worker --coordinator http://127.0.0.1:8080 +``` + +The worker reaches the model layer too, so if your provider needs an API key, export the variable named by `team_harness_api_key_env` in the worker's shell as well as the coordinator's. See [Configuration](/docs/configuration). + +## loopy status + +Prints the state of the latest session: overall status, session id, completed iteration count, the current task (workflow id, iteration, session, and start time), and the stop reason if any. If there is no state yet, it says so. + +```bash +loopy status +``` + +```text +status: running +session: 20260712T193000Z-a1b2c3d4e5f6 +iteration_count: 12 +current_task: inner (iteration 12, session 20260712T193000Z-a1b2c3d4e5f6, started 2026-07-12T19:41:07Z) +stop_reason: none +``` + +`status` takes no options. + +## loopy stop + +Requests a graceful stop by setting `stop_requested=true` in the latest session-local state. Running workers exit after their next check-in with the coordinator; the coordinator brings the session to a terminal state. It prints `stop requested` on success, and errors if there is no state to stop. + +```bash +loopy stop +``` + +`stop` takes no options. For how a *workflow* stops the loop on its own — versus this operator-initiated stop — see [Success & Control](/docs/success-and-control). + +## Where to go next + +- [Getting Started](/docs/getting-started) — these commands in a full first-run walkthrough. +- [Configuration](/docs/configuration) — what `coordinator` reads at startup. +- [Troubleshooting](/docs/troubleshooting) — resolving preflight, resume, and API-key errors. diff --git a/website/src/app/docs/concepts/page.mdx b/website/src/app/docs/concepts/page.mdx new file mode 100644 index 0000000..9c3cc6c --- /dev/null +++ b/website/src/app/docs/concepts/page.mdx @@ -0,0 +1,63 @@ +export const metadata = { + title: "Concepts", + description: + "The mental model behind loopy-loop: the coordinator and worker split, delegation to team-harness, the iteration loop, and the session directory as durable state.", +}; + +# Concepts + +This page explains how loopy-loop is put together and why. Once you have the mental model — a coordinator that decides, workers that execute, and a session directory that remembers — the configuration and workflow pages read as details rather than surprises. + +## Coordinator and worker + +loopy-loop separates *deciding what to do next* from *doing it*. Those are two long-running processes with sharply different jobs. + +The **coordinator** is a small FastAPI server. It owns the loop state, loads your `loopy_loop_config.yaml`, resolves the goal, and creates a session. On each request it chooses the next workflow to run based on scheduling rules and the history so far, records results, checks the session's control and eval artifacts, and decides whether to continue or stop. The coordinator never runs agents itself. + +A **worker** is a blocking executor. It asks the coordinator for a task, renders that workflow's prompt with the concrete session paths, runs it, writes the outputs into the iteration directory, and reports back. Then it asks for the next task. A worker holds no loop state of its own — if it crashes, the coordinator notices the orphaned task on the next check-in and dispatches fresh work. You can run several workers against one coordinator. + +This split is what makes the loop durable and inspectable: the coordinator's decisions live in files, and the worker's work products live in files, so nothing important is trapped inside a process. + +## Delegation to team-harness + +Workers do not talk to model providers directly. They run each assignment through [`team-harness`](https://github.com/writeitai/team-harness), the orchestration layer that manages a coordinator model and can spawn external agent CLIs such as Codex, Claude Code, and Gemini as worker subprocesses. + +Concretely, the worker calls `TeamHarness.run(...)` with the rendered workflow prompt. team-harness runs its own coordinator model, optionally delegates to one or more agent CLIs, and returns a result. loopy-loop then normalizes and stores that result. Everything about *which* provider, model, and agents team-harness uses comes from the `team_harness_*` fields in your config — see [Configuration](/docs/configuration). + +## The iteration loop + +Each iteration runs exactly one workflow. Across many iterations, a well-designed workflow set forms a repeating cycle: + +1. **Plan** — a planning workflow reviews durable state and decides the next unit of work. +2. **Implement** — an implementation workflow makes the change in the target repo. +3. **Evaluate** — an eval workflow checks progress against the goal and writes evidence. +4. **Record** — every iteration writes its prompt, result, and artifacts to disk. +5. **Continue** — the coordinator picks the next eligible workflow, or stops. + +The coordinator decides *which* workflow is eligible each turn using per-workflow scheduling fields such as `priority`, `run_every`, `must_follow`, and `run_after_successes`. The recommended `inner_outer_eval` template implements this exact rhythm: `outer` plans, `inner` implements, and `eval_reviewer`/`eval_runner` evaluate. The full scheduling model lives in [Workflows](/docs/workflows), and the stop logic lives in [Success & Control](/docs/success-and-control). + +## The session directory as durable state + +loopy-loop deliberately does not hide state inside a chat transcript. Continuity comes from two places: your git history, and files under `.loopy_loop/sessions//`. + +Each fresh coordinator run creates one session directory. Its id starts with a UTC timestamp and includes a deterministic hash of the goal, so sessions sort chronologically and similar goals are easy to compare. Inside, the coordinator and workflows keep their state in named files: + +- `goal.md` — the exact goal text copied into the session. +- `state.json` — coordinator-owned dispatch state. +- `control.json` — the workflow-owned stop switch. +- `project_state/` — workflow-owned durable markdown state that survives across iterations. +- `iterations/_/` — one directory per assignment, holding the rendered prompt, the normalized result, and a link to the underlying harness output. + +Because state is files, you can read it, diff it, resume from it, and audit it. The [Session Layout](/docs/session-layout) reference documents every file and its owner. + +## The two-endpoint model + +At a conceptual level, the coordinator and worker communicate with a simple ping-pong: the worker asks once for a task, then reports each completed task and receives the next one, until it is told to stop. There is no polling, no leases, and no worker identity to track — every response tells the worker either to run a specific workflow or to stop. + +That is all you need to hold in mind here. The exact JSON payloads, the two endpoints, and crash-recovery behavior are documented in the [HTTP Contract](/docs/http-contract). + +## Where to go next + +- [Configuration](/docs/configuration) — the root config and every provider/model setting. +- [Workflows](/docs/workflows) — how workflows are defined, scheduled, and grouped into sets. +- [Success & Control](/docs/success-and-control) — how the loop decides it is done. diff --git a/website/src/app/docs/configuration/page.mdx b/website/src/app/docs/configuration/page.mdx new file mode 100644 index 0000000..65f3e0e --- /dev/null +++ b/website/src/app/docs/configuration/page.mdx @@ -0,0 +1,116 @@ +export const metadata = { + title: "Configuration", + description: + "The root loopy_loop_config.yaml: goal file, workflow set, turn limits, and every team_harness_* provider, model, agent, and retry setting.", +}; + +# Configuration + +Every loop is configured by a single root file, `loopy_loop_config.yaml`, at the top of your target repository. It names the goal file, selects a workflow set, caps how long the loop may run, and tells `team-harness` which provider, model, and agents to use. This page documents every field the coordinator reads at startup. + +Per-workflow scheduling lives in a separate `config.yaml` beside each workflow prompt — that is covered in [Workflows](/docs/workflows). + +## The root config file + +A typical `loopy_loop_config.yaml` looks like this: + +```yaml +goal_file: loopy_loop_goal.txt +workflow_set: inner_outer_eval +max_turns: 160 +goal_check_consecutive_failures_cap: 3 +team_harness_provider: "codex" +team_harness_model: "gpt-5.5" +team_harness_agents: + - "codex" + - "claude" + - "gemini" +team_harness_agent_models: + codex: "gpt-5.5" + claude: "claude-opus-4-8" + gemini: "gemini-3.5-flash" +team_harness_agent_reasoning_efforts: + codex: "high" +team_harness_api_base: "https://openrouter.ai/api/v1" +team_harness_api_key_env: "OPENROUTER_API_KEY" +``` + +## Core fields + +These fields control the goal, the workflow set, and how long the loop runs. + +| Field | Type | Default | Description | +| --- | --- | --- | --- | +| `goal_file` | string | required | Path to the goal file, resolved relative to `loopy_loop_config.yaml`. Its contents are copied into each session as `goal.md`. | +| `workflow_set` | string | required | The workflow set used for new sessions when the coordinator is started without an override. | +| `max_turns` | integer | required | Maximum number of completed workflow iterations before the loop stops. | +| `goal_check_consecutive_failures_cap` | integer | `3` | How many consecutive invalid or missing goal-check outputs are tolerated before the loop stops with `goal_check_broken`. Must be at least `1`. | + +The goal must live in a file. See the [Success & Control](/docs/success-and-control) page for how `max_turns` and the failure cap participate in stopping the loop. + +## team-harness settings + +Everything about *how* work is executed — which provider, coordinator model, and agent CLIs `team-harness` uses — comes from the `team_harness_*` fields. Field names are exact; a typo becomes an "unknown field" error rather than a silently ignored setting. + +| Field | Type | Default | Description | +| --- | --- | --- | --- | +| `team_harness_provider` | string | `"openai_compat"` | The team-harness provider name workers use. `codex` uses local Codex authentication; the shipped templates set this to `"codex"`. | +| `team_harness_model` | string | `"gpt-5.5"` | The model name passed to team-harness for its **coordinator** model. | +| `team_harness_agents` | list of strings | `["codex"]` | Agent names team-harness should make available as worker subprocesses, such as `codex`, `claude`, and `gemini`. | +| `team_harness_agent_models` | map | `{}` | Per-agent default worker model overrides, keyed by agent name. | +| `team_harness_agent_reasoning_efforts` | map | `{}` | Per-agent reasoning-effort overrides, keyed by agent name. Only agents whose templates support a reasoning-effort flag use this value. | +| `team_harness_api_base` | string | `"https://openrouter.ai/api/v1"` | The OpenAI-compatible API base URL passed to team-harness. Normalized on load (see below). | +| `team_harness_api_key_env` | string | `"OPENROUTER_API_KEY"` | Name of the environment variable that holds the API key. | +| `team_harness_system_prompt_extension` | string | `""` | Extra system-prompt text appended for every harness run. The templates use this to state session-state and PR/merge policy. | + +### Retry controls + +These optional fields tune how team-harness retries transient API and network errors on its coordinator model. Leave them unset to use the installed team-harness defaults. + +| Field | Type | Default | Description | +| --- | --- | --- | --- | +| `team_harness_max_retries` | integer | unset | Coordinator retry budget. Must be `0` or greater. | +| `team_harness_retry_base_delay_s` | number | unset | Base delay in seconds for retry backoff. Must be greater than `0`. | +| `team_harness_retry_max_delay_s` | number | unset | Maximum delay in seconds for retry backoff. Must be greater than `0`, and no less than `team_harness_retry_base_delay_s`. | + +The `inner_outer_eval` template ships these as commented-out examples so you can see the shape before enabling them. + +### Optional criteria fields + +The config also accepts two optional list fields, `completion_criteria` and `stop_criteria`, both defaulting to an empty list. They record observable criteria for reference by your workflows; the loop's actual stop decision is driven by `control.json`, not by these lists. Most projects keep completion criteria in the goal file instead. + +## API base normalization + +loopy-loop normalizes `team_harness_api_base` on load so you can write whichever form you prefer: any trailing slash is stripped, and `/v1` is appended when it is missing. Both of these resolve to `https://openrouter.ai/api/v1`: + +```yaml +team_harness_api_base: "https://openrouter.ai/api/" +team_harness_api_base: "https://openrouter.ai/api/v1" +``` + +## API keys and providers + +Whether an API key is required depends on the provider. The `codex` provider skips the API-key check entirely and relies on local Codex authentication. For any other provider, the environment variable named by `team_harness_api_key_env` must be exported — and it must be exported in **both** the coordinator shell and every worker shell, because both processes reach the model layer. + +```bash +export OPENROUTER_API_KEY=sk-... +``` + +If that variable is missing for a provider that needs it, the coordinator's startup preflight fails with a clear error before any session is created. + +## Validation rules + +The root config is parsed strictly, which turns silent mistakes into loud, early errors: + +- **Unknown keys are rejected.** Every field name above is exact — misspellings fail preflight rather than being ignored. +- **Inline `goal` is rejected.** The goal must live in a file referenced by `goal_file`. A top-level `goal:` key is an explicit error that points you at `goal_file`. +- **`goal_file` must resolve to a non-empty file.** It is read relative to the config file, and an empty goal is rejected. +- **`workflow_set` must not be empty.** +- **Agent maps require non-empty keys and values.** Empty strings in `team_harness_agent_models` or `team_harness_agent_reasoning_efforts` are rejected. +- **Retry bounds are checked.** `team_harness_retry_max_delay_s` must be at least `team_harness_retry_base_delay_s`. + +## Where to go next + +- [Workflows](/docs/workflows) — define workflow sets and per-workflow scheduling. +- [Getting Started](/docs/getting-started) — scaffold a config with `loopy init`. +- [Troubleshooting](/docs/troubleshooting) — resolve preflight and API-key errors. diff --git a/website/src/app/docs/evaluation/page.mdx b/website/src/app/docs/evaluation/page.mdx new file mode 100644 index 0000000..f7e0ffb --- /dev/null +++ b/website/src/app/docs/evaluation/page.mdx @@ -0,0 +1,110 @@ +export const metadata = { + title: "Evaluation", + description: "How loopy-loop judges whether work is good: LLM-as-judge checks, why agents may not author deterministic checks, and where repo-owned tests fit in.", +}; + +# Evaluation + +[Success & Control](/docs/success-and-control) established that an iteration's +`success` flag only means the harness ran — it says nothing about whether the work +is good. This page is about the layer that answers that second question. In +loopy-loop's packaged workflow set, evaluation is **LLM-as-judge**: a model +evaluates the output against a described outcome, and records its verdict as +evidence. This page explains how that works, one rule that surprises people (agents +are forbidden from authoring deterministic checks), and exactly where the boundary +of that rule sits. + +## LLM-as-judge with eval-banana + +The packaged `inner_outer_eval` template evaluates work using +[`eval-banana`](https://github.com/writeitai/eval-banana) conventions. The eval +workflows create session-scoped `harness_judge` checks — natural-language +descriptions of what a good outcome looks like — and a later workflow runs those +checks and writes the per-iteration `goal_check.json` artifact. + +Two workflows split the job: + +- `eval_reviewer` creates or refreshes the session-scoped eval-banana checks under + the session `eval_checks/` directory. +- `eval_runner` runs those checks and writes `goal_check.json`. It is the workflow + configured with `emits_goal_check: true`. + +Checks and results live inside the session so they travel with the run: + +```bash +eval-banana run \ + --cwd . \ + --check-dir .loopy_loop/sessions//eval_checks \ + --output-dir .loopy_loop/sessions//eval_results +``` + +Remember from [Success & Control](/docs/success-and-control): a passing eval is +evidence, not a stop switch. `goal_check.json` records the verdict; a workflow +still has to update `control.json` to actually stop the loop. + +## Agents do not author deterministic checks + +In the stock `inner_outer_eval` template, the eval workflows may create **only** +`harness_judge` checks that describe desired outcomes. Authoring deterministic +checks is explicitly forbidden. The `eval_reviewer` prompt says it plainly: "Only +create harness_judge checks"; "Do not create deterministic checks. Deterministic +checks are forbidden." + +This rule comes from direct experience, not theory. When agents were allowed to +**author** their own deterministic checks, they wrote bad ones: + +- Brittle string-matching that tested the surface, not the behavior. +- Checks that tested the wrong thing entirely. +- Checks that passed for the wrong reason. +- Checks an agent could trivially satisfy without doing the real work. + +In short, letting the implementer invent its own pass/fail criteria let it game +itself — it could quietly redefine "done" into something it had already produced. +An LLM judge evaluating a described *outcome* removes that failure mode: the check +states what good looks like in natural language, and the implementer cannot +silently rewrite the target. + +## The boundary: invented checks vs. the repo's own tests + +This rule is narrower than it first sounds, and getting the boundary right matters +when you apply loopy-loop to a real repository. Two very different things often get +lumped together under "deterministic check": + +- **An agent invents a check** — the failure mode above. Correctly forbidden. +- **Running a check the repo already owns** — `uv run pytest`, `import-linter`, + `alembic upgrade`, `make test`, evaluated on exit code. The agent did not invent + these; they are the project's own contract. Running them is deterministic, but it + is **not** the failure mode the rule targets. + +So the "deterministic forbidden" rule is correct for generic target repos, where +the only deterministic checks available would be agent-invented ones. But for a +repo that already owns a trustworthy contract-test suite, the right configuration +is *both*: LLM-as-judge for the qualitative "did this achieve the outcome," **and** +a deterministic gate that shells out to the repo's own suite as a backstop under +the judge. That backstop does not reintroduce the agent-authoring problem, and it +removes the single-judgment point of failure. + +When you want that, override the stock rule in a dedicated child workflow set +rather than loosening the packaged template. See [Workflows](/docs/workflows) for +how workflow sets are structured. + +## Choosing and trusting the judge + +LLM-as-judge buys resistance to self-gaming at the cost of the usual LLM +properties: non-determinism, per-check inference cost, and the judge itself as a +point of trust. A few guidelines follow from that: + +- **Judge with a different model family than the implementer.** The judge should + not share failure modes with the model that wrote the change. If the same family + implements and grades, a blind spot in one is a blind spot in both. +- **Treat a single judge pass as evidence, not a hard gate for high-stakes stops.** + Keep `control.json` as the stop switch. For high-stakes goals, require repeated + or independent judgments, or add the repo-owned deterministic backstop described + above, before a terminal `goal_met`. +- **Configure this per target repo, not globally.** When a target owns a + trustworthy contract suite, add the deterministic backstop; do not remove the + judge. When judge cost or flakiness becomes material, add repetition and + cross-family judging rather than abandoning the approach. + +For how the verdict is recorded and how the loop actually stops on it, return to +[Success & Control](/docs/success-and-control). diff --git a/website/src/app/docs/getting-started/page.mdx b/website/src/app/docs/getting-started/page.mdx new file mode 100644 index 0000000..1e8223c --- /dev/null +++ b/website/src/app/docs/getting-started/page.mdx @@ -0,0 +1,122 @@ +export const metadata = { + title: "Getting Started", + description: + "Install loopy-loop, scaffold a target repository, write a goal, and run your first coordinator and worker.", +}; + +# Getting Started + +This page takes you from an empty terminal to a running loop: install the CLI, scaffold a target repository, write the goal, and start the coordinator and worker. It assumes you already have a git repository you want agents to work on. + +## Install the CLI + +Install from the official [PyPI package](https://pypi.org/project/loopy-loop/). With [`uv`](https://docs.astral.sh/uv/), install it as a command-line tool: + +```bash +uv tool install loopy-loop +``` + +Or with `pip`: + +```bash +pip install loopy-loop +``` + +The CLI exposes two equivalent commands, `loopy` and `loopy-loop`. This documentation uses `loopy` throughout. + +If you are working inside a checkout of the loopy-loop repository itself, install the development dependencies instead: + +```bash +uv sync --extra dev +``` + +## Install the Agent Skill + +loopy-loop also ships an [Agent Skill](https://support.claude.com/en/articles/12512176-what-are-skills) that teaches Claude Code, Codex, and compatible agents how to set up and run loopy-loop in a target repo. Install it with: + +```bash +npx skills add https://github.com/writeitai/loopy-loop --skill loopy-loop +``` + +This is optional — it helps an agent operate loopy-loop for you, but you can run every command below by hand. + +## Scaffold a target repo + +Run `loopy init` from the root of the repository you want agents to work on. The `inner_outer_eval` template is the recommended starting point: + +```bash +loopy init --template inner_outer_eval +``` + +This creates a small set of files and leaves anything that already exists untouched (`loopy init` is idempotent): + +- `loopy_loop_config.yaml` — the root config. +- `loopy_loop_goal.txt` — the goal file. +- `.loopy_loop/workflow_sets/inner_outer_eval/workflows/` — the `outer`, `inner`, `eval_reviewer`, and `eval_runner` workflows. +- a `.gitignore` entry for `.loopy_loop/sessions/`. + +Workflow definitions are part of your repo and should usually be committed. Session directories are runtime output and are ignored by default. See [Workflows](/docs/workflows) for the other templates (`default` and `pm_planner_dispatcher`) and when to pick each. + +## Write the goal + +The loop goal lives in `loopy_loop_goal.txt`. Replace the scaffolded example with the real target, including constraints and observable completion criteria. Keep it specific enough that a reviewer or eval workflow can decide whether the loop is done. + +```text +Implement passwordless email login. + +Completion criteria: +- Users can request a one-time login link from the sign-in page. +- The link expires after 15 minutes and cannot be reused. +- Existing password login keeps working. +- Tests cover token expiry, token reuse, and successful login. +- README documents required environment variables. +``` + +The goal must live in a file — inline `goal:` values in `loopy_loop_config.yaml` are rejected. For a one-off override, start the coordinator with `--goal-file PATH`; that file is copied into the session as `goal.md`. + +## Run the loop + +loopy-loop runs as two processes: a coordinator that owns the loop state and one or more workers that execute assignments. Start them in separate terminals. + +The default templates use `team_harness_provider: "codex"`, which relies on your local Codex authentication. If you switch to an OpenAI-compatible provider, export the environment variable named in `team_harness_api_key_env` (usually `OPENROUTER_API_KEY`) in **both** the coordinator and worker shells. + +Start the coordinator: + +```bash +loopy coordinator --host 127.0.0.1 --port 8080 +``` + +Start a worker in another terminal, pointing it at the coordinator: + +```bash +loopy worker --coordinator http://127.0.0.1:8080 +``` + +The worker asks the coordinator for a task, runs it through `team-harness`, reports the result, and repeats until the coordinator tells it to stop. Multiple workers can share one coordinator. + +## Monitor, stop, and resume + +From the repo root, check on the run or ask it to wind down: + +```bash +loopy status +loopy stop +``` + +`loopy status` prints the latest session id, iteration count, current task, and stop reason. `loopy stop` requests a graceful stop; workers exit after their next check-in. + +If the coordinator process dies while a session is still running, the state file stays marked `running`. Reattach to it with `--resume`: + +```bash +loopy coordinator --host 127.0.0.1 --port 8080 --resume +``` + +Without `--resume`, starting a coordinator against a still-running session is intentionally fatal, so you never accidentally run two coordinators over the same state. + +## Next steps + +- [Concepts](/docs/concepts) — understand the coordinator/worker split and the iteration loop before you customize anything. +- [Configuration](/docs/configuration) — tune models, providers, and turn limits in `loopy_loop_config.yaml`. +- [Workflows](/docs/workflows) — edit workflow prompts and scheduling to shape how the loop behaves. +- [Success & Control](/docs/success-and-control) — learn exactly how the loop decides it is finished. +- [CLI Reference](/docs/cli-reference) and [Troubleshooting](/docs/troubleshooting) — the full command surface and common failure modes. diff --git a/website/src/app/docs/http-contract/page.mdx b/website/src/app/docs/http-contract/page.mdx new file mode 100644 index 0000000..3863183 --- /dev/null +++ b/website/src/app/docs/http-contract/page.mdx @@ -0,0 +1,172 @@ +export const metadata = { + title: "HTTP Contract", + description: "The two coordinator endpoints — POST /register and POST /finished — with exact request and response payloads, stale-response handling, and crash recovery.", +}; + +# HTTP Contract + +The coordinator exposes exactly two HTTP endpoints, and a worker only ever talks to +these two. `POST /register` asks for the next assignment; `POST /finished` reports a +completed one and receives the next. Both return the same `TaskResponse` shape, and +both carry the whole loop's decision-making in a small, precise contract. This page +documents that contract exactly — the payloads, the config snapshot, and the rules +that make it safe across worker crashes and stale retries. + +## The response shape + +Every response from either endpoint is a `TaskResponse` whose `action` is either +`"run"` or `"stop"`. + +- A **run** response carries `workflow_set`, `workflow_id`, `session_id`, + `iteration`, and a `config_snapshot`. The worker loads the named workflow, renders + its prompt, and executes it. +- A **stop** response carries only a `stop_reason`; the other fields are `null`. + The worker exits. + +## POST /register + +A worker calls `/register` to receive its first assignment. The request body is +empty. + +Request: `{}` + +Run response: + +```json +{ + "action": "run", + "workflow_set": "main", + "workflow_id": "planner", + "session_id": "20260419_143022_71393ee22450_ab12cd34", + "iteration": 3, + "config_snapshot": { + "goal": "Ship a minimal working landing page", + "goal_hash": "71393ee22450", + "workflow_set": "main", + "completion_criteria": ["Homepage renders without errors"], + "stop_criteria": ["A workflow updates session control.json to stopped"], + "max_turns": 20, + "goal_check_consecutive_failures_cap": 3, + "team_harness_provider": "openai_compat", + "team_harness_model": "gpt-5.5", + "team_harness_agents": ["codex"], + "team_harness_agent_models": {"codex": "gpt-5.5"}, + "team_harness_agent_reasoning_efforts": {"codex": "high"}, + "team_harness_max_retries": null, + "team_harness_retry_base_delay_s": null, + "team_harness_retry_max_delay_s": null, + "team_harness_api_base": "https://openrouter.ai/api/v1", + "team_harness_api_key_env": "OPENROUTER_API_KEY", + "team_harness_system_prompt_extension": "" + }, + "stop_reason": null +} +``` + +Stop response: + +```json +{ + "action": "stop", + "stop_reason": "goal_met", + "workflow_set": null, + "workflow_id": null, + "session_id": null, + "iteration": null, + "config_snapshot": null +} +``` + +Rules for `/register`: + +- `config_snapshot.goal` is the resolved goal **text**, loaded from the `goal_file` + named in `loopy_loop_config.yaml`. Workers and team-harness never receive the goal + file path as the goal. +- `workflow_set` tells the worker which + `.loopy_loop/workflow_sets//workflows//` directory to + load. +- If a task is already current — meaning the previous worker crashed without calling + `/finished` — `/register` first checks that iteration directory for + `pending_finished_request.json` or `result.json`. If either proves the task + completed, the coordinator records the completed task in history before checking + stop conditions. Only a task with no recoverable local completion is recorded as + failed with `error="abandoned"`. See [Crash recovery](#crash-recovery-and-stale-retries). +- If the loop is already in a terminal state, `/register` immediately returns + `action: "stop"`. + +## The config snapshot + +The `config_snapshot` is the coordinator's resolved, immutable view of the run's +configuration, sent with every run response so the worker never has to re-read the +repo config. Its most notable fields: + +- `goal` — the resolved goal text (never a file path). +- `goal_hash` — the deterministic hash of the goal, also embedded in the session id. +- `max_turns` and `goal_check_consecutive_failures_cap` — the loop's stop limits. +- The `team_harness_*` fields — provider, model, agents, per-agent model and + reasoning overrides, retry controls, API base, the name of the API key env var, + and any system prompt extension. + +For where these come from and how they are validated, see +[Concepts](/docs/concepts) and [Configuration](/docs/configuration). + +## POST /finished + +A worker calls `/finished` to report a completed assignment. The response is the +same `TaskResponse` shape as `/register`, so the worker learns its next task (or to +stop) in the same round trip. + +Request: + +```json +{ + "workflow_id": "planner", + "session_id": "20260419_143022_71393ee22450_ab12cd34", + "iteration": 3, + "success": true, + "text": "done", + "error": null +} +``` + +Response: same shape as the `/register` response (`action` is `"run"` or `"stop"`). + +Rules for `/finished`: + +- **Match required.** The report must match the current task on `session_id`, + `workflow_id`, and `iteration`. Only then does the coordinator record the result, + read the session artifacts, and dispatch the next task. +- **`control.json` and `goal_check.json`.** After a matched finish, the coordinator + reads `control.json` only from the session directory, and reads `goal_check.json` + only from the current iteration directory when the workflow is `goal_check` or has + `emits_goal_check: true`. A valid `goal_check.json` is an eval artifact, not a stop + switch — workflows stop the loop by updating `control.json`. See + [Success & Control](/docs/success-and-control). +- **Child sessions.** A parent workflow can request a depth-first child loop by + writing a `schema_version: 1` JSON request with `workflow_set` and `goal` under + the session's `child_requests/` directory. The next response dispatches the child + workflow set; after the child stops, the coordinator resumes the parent. See + [Child Sessions](/docs/child-sessions). + +## Crash recovery and stale retries + +The contract is designed so that a killed worker, a duplicate retry, or a lost +acknowledgment never corrupts loop state. + +**Stale finish (mismatch).** If a `/finished` call's `session_id` + +`workflow_id` + `iteration` does not match the current task, the call is stale. The +coordinator does **not** mutate state and does **not** change the current task; it +returns the current task's run response so the caller sees what is actually running. + +**Finish with no active task.** If `/finished` arrives when no task is current, the +coordinator dispatches the next available task as if `/register` had been called — +or returns `action: "stop"` if the state is terminal. This makes a post-crash retry +safe. + +**Worker crash mid-handoff.** A worker writes `result.json`, then +`pending_finished_request.json`, then calls `/finished`. If it dies inside that +window, the next `/register` recovers the completed result from +`pending_finished_request.json` (or reconstructs it from `result.json` if the +pending file is missing) and records the task as completed — rather than marking it +`abandoned`. A locally written result is treated as authoritative. The per-iteration +files behind this are described in [Session Layout](/docs/session-layout). diff --git a/website/src/app/docs/layout.tsx b/website/src/app/docs/layout.tsx new file mode 100644 index 0000000..e0dfec8 --- /dev/null +++ b/website/src/app/docs/layout.tsx @@ -0,0 +1,46 @@ +import { DocsSidebar } from "@/components/docs/DocsSidebar"; +import { DocsTableOfContents } from "@/components/docs/DocsTableOfContents"; +import { DocsPageNavigation } from "@/components/docs/DocsPageNavigation"; +import { MobileDocsSidebar } from "@/components/docs/MobileDocsSidebar"; + +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {/* Sidebar — hidden on mobile */} + + + {/* Main content */} +
+
+ +
+ + {/* data-pagefind-body scopes the search index to page content only. */} +
+ {children} +
+ +
+ + {/* On this page — hidden on smaller screens */} + +
+
+ ); +} diff --git a/website/src/app/docs/page.mdx b/website/src/app/docs/page.mdx new file mode 100644 index 0000000..786962d --- /dev/null +++ b/website/src/app/docs/page.mdx @@ -0,0 +1,37 @@ +export const metadata = { + title: "Introduction", + description: + "loopy-loop runs long-running AI agent workflows inside your repository, with durable session state, repeatable prompts, and explicit stop conditions.", +}; + +# Introduction + +`loopy-loop` runs long-running AI agent workflows inside your repository. It turns a goal file into an inspectable sequence of agent iterations — plan, implement, evaluate, record evidence, and continue — until the goal is met or the loop hits a terminal blocker. + +It is a small Python CLI plus a [FastAPI](https://fastapi.tiangolo.com/) coordinator and one or more workers. The coordinator owns the loop state and decides the next workflow; the workers execute assignments through [`team-harness`](https://github.com/writeitai/team-harness), which can delegate to agent CLIs such as Codex, Claude Code, and Gemini. + +## Why loopy-loop + +The value is control and durability. Instead of asking one agent to solve a large task in a single fragile chat, loopy-loop gives the work a home in your repo: + +- **Durable state in git and files.** Continuity comes from your git history plus files under `.loopy_loop/sessions//`, not from a chat transcript you can't audit. You can pause, resume, and inspect every prompt/result pair. +- **Repeatable workflow prompts.** Each iteration runs one named workflow whose prompt lives in your repo and is version-controlled like any other source. +- **Explicit stop conditions.** The loop stops when a workflow says so through a session-scoped control file, or when it exhausts its turn budget — never by guesswork. +- **Structured logs.** Every iteration writes its rendered prompt, normalized result, and a link to the underlying harness output, so you can see exactly what happened and why. + +The actual project changes still land where they belong: normal git branches and pull requests. + +## Who it's for + +loopy-loop is for developers who want to point capable coding agents at a substantial, well-specified goal and let them work across many iterations without losing visibility or control. If you can describe a target with observable completion criteria — and you want the work to be resumable, auditable, and reviewable — this is built for you. + +## Where to go next + +- [Getting Started](/docs/getting-started) — install the CLI, scaffold a repo, and run your first loop. +- [Concepts](/docs/concepts) — the coordinator/worker model, the iteration loop, and the session directory as durable state. +- [Configuration](/docs/configuration) — the root `loopy_loop_config.yaml` and every `team_harness_*` setting. +- [Workflows](/docs/workflows) — workflow sets, scheduling fields, and the three shipped templates. +- [Success & Control](/docs/success-and-control) and [Evaluation](/docs/evaluation) — how the loop decides it is done. +- Reference: [Session Layout](/docs/session-layout), [HTTP Contract](/docs/http-contract), and [CLI Reference](/docs/cli-reference). + +The source lives on [GitHub](https://github.com/writeitai/loopy-loop) and the package is published to [PyPI](https://pypi.org/project/loopy-loop/). diff --git a/website/src/app/docs/session-layout/page.mdx b/website/src/app/docs/session-layout/page.mdx new file mode 100644 index 0000000..14a1973 --- /dev/null +++ b/website/src/app/docs/session-layout/page.mdx @@ -0,0 +1,191 @@ +export const metadata = { + title: "Session Layout", + description: "A reference for the loopy-loop session directory: the session id format, the session-level files, and the per-iteration files the loop writes.", +}; + +# Session Layout + +loopy-loop keeps its state on disk, not inside a chat transcript. One session +directory is created per fresh coordinator run and reused for every iteration in +that session. Everything you need to audit, pause, or resume a run lives under that +directory. This page is the reference for its shape: the session id format, the +session-level files, and the per-iteration files. + +## The session id + +New session ids follow this format: + +```text +___ +``` + +For example, `20260419_143022_71393ee22450_ab12cd34`. The leading UTC timestamp +means session directories sort chronologically by name. The `goal_hash` is derived +deterministically from the goal text, so runs against the same goal are easy to +spot and compare, and the trailing random suffix keeps ids unique. + +## Directory tree + +A session directory looks like this: + +```text +.loopy_loop/ +└── sessions/ + └── / + ├── session.json + ├── events.jsonl + ├── control.json + ├── updates_from_user.md + ├── project_state/ + │ └── finished.md + ├── eval_checks/ + ├── eval_results/ + │ └── / + │ ├── report.json + │ ├── report.md + │ └── checks/ + ├── harness_outputs/ + │ └── 0001_planner/ + │ └── / + └── iterations/ + ├── 0001_planner/ + │ ├── prompt.txt + │ ├── result.json + │ ├── result_text.txt + │ ├── harness_run_id.txt + │ └── pending_finished_request.json + └── 0002_goal_check/ + ├── prompt.txt + ├── result.json + ├── result_text.txt + ├── harness_run_id.txt + └── goal_check.json +``` + +Sessions that spawn child work also grow a `child_requests/` inbox and a +`children/` directory; those are covered on [Child Sessions](/docs/child-sessions). + +## Session files + +**`session.json`** — Session metadata, written once when the directory is created. +It contains `session_id`, `goal_hash`, and `created_at`. + +**`events.jsonl`** — A reserved append-only event log for diagnostics, created at +session start in v1. + +**`control.json`** — The session-scoped workflow stop switch, created with +`state: "running"` when the session starts. Workflows update it only when they want +the loop to stop, and the coordinator reads it only from the session directory. It +is the single gate that ends a run — see [Success & Control](/docs/success-and-control) +for the full contract and stop reasons. + +**`updates_from_user.md`** — A human-writable inbox for requests that arrive after +the session starts. The outer workflow treats non-empty content as highest-priority +planning input, and clears the file only after reflecting the request into +`project_state/`. + +**`project_state/`** — Optional workflow-owned durable markdown state for reusable +workflows. `loopy_loop_goal.txt` remains the source of truth for the target, +constraints, and completion intent; do not copy or restate the goal into +`project_state/`. Common files include `README.md`, `memory.md`, +`current_state.md`, `what_we_have.md`, `decisions.md`, `eval_results.md`, +`finished.md`, and `what_we_should_do/plan.md`. The `README.md` should document +ownership rules — `memory.md` for essential durable facts only, `finished.md` for +outer-owned accepted completions only, `eval_results.md` for eval detail, and +`current_state.md` for live status, the latest eval headline, and the next action. +The coordinator does not parse any of these files; it only provides the paths. + +**`project_state/finished.md`** — An append-only ledger for outer-verified +completed work. Each entry should summarize the completed task and link to the +relevant iteration and harness output files. For implementation work, entries +should include delivery evidence for each changed repo: repo path or remote, +branch, PR URL, merge status, merge commit when merged, and checks/CI status. The +default implementation delivery is branch + PR + passing checks + merge, unless the +task is session-state-only, eval-only, research-only, planning-only, or has no +usable remote/auth. + +**`eval_checks/`** — Optional workflow-owned eval-banana checks for this session. A +workflow can run just these checks and write the results into the session: + +```bash +eval-banana run \ + --cwd . \ + --check-dir .loopy_loop/sessions//eval_checks \ + --output-dir .loopy_loop/sessions//eval_results +``` + +**`eval_results/`** — Optional workflow-owned eval-banana run output. Each +`eval-banana run --output-dir .../eval_results` creates a child +`/` directory containing `report.json`, `report.md`, and +per-check artifacts. `project_state/eval_results.md` should summarize and link +these reports rather than copy the raw output, and `project_state/current_state.md` +should carry only the latest eval headline and the next action. + +**`harness_outputs/`** — The root for team-harness coordinator and worker +artifacts. Each iteration gets its own output root, keyed by iteration number and +workflow id: + +```text +.loopy_loop/sessions//harness_outputs/_/ +``` + +team-harness then creates a child directory named with its own run id: + +```text +.loopy_loop/sessions//harness_outputs/_// +``` + +## Iteration files + +Each iteration gets its own directory under `iterations/`, named +`_` (for example, `0001_planner`). It holds the record of that +single assignment. + +**`prompt.txt`** — The rendered assignment prompt sent to +`TeamHarness.run(task=...)`. This is exactly what the workflow was asked to do, +with all session paths already substituted in. + +**`result.json`** — loopy-loop's normalized result payload for the iteration: + +```json +{ + "success": true, + "text": "final response", + "error": null, + "harness_run_id": "run-123", + "harness_output_dir": ".loopy_loop/sessions//harness_outputs/0001_planner/run-123" +} +``` + +Remember that `success` here means the harness ran, not that the work is good — see +[Success & Control](/docs/success-and-control). + +**`result_text.txt`** — A plain-text copy of the result text, or an empty string on +failure. + +**`harness_run_id.txt`** — The team-harness run id, which links this iteration to +its output under `harness_outputs/`. Empty when a failure occurred before a run id +existed. + +**`pending_finished_request.json`** — A durable handoff record written after +`result.json` and before the worker calls `/finished`. It is removed once +`/finished` is acknowledged or once `/register` recovers it. If a worker exits in +that handoff window, the next `/register` uses this file to record the completed +task instead of marking it `abandoned`; if it is missing but `result.json` exists +for the active task, the coordinator reconstructs the finished request from +`result.json`. See [HTTP Contract](/docs/http-contract) for the recovery rules. + +**`goal_check.json`** — A per-iteration eval artifact, present only for the +reserved `goal_check` workflow or a workflow configured with +`emits_goal_check: true`. Its schema in v1: + +```json +{ + "goal_met": false, + "reason": "CTA exists, but deployment docs are still missing.", + "schema_version": 1 +} +``` + +A valid `goal_check.json` is evidence, not a stop switch. A workflow that wants to +stop the loop must update the session `control.json`. diff --git a/website/src/app/docs/success-and-control/page.mdx b/website/src/app/docs/success-and-control/page.mdx new file mode 100644 index 0000000..4944161 --- /dev/null +++ b/website/src/app/docs/success-and-control/page.mdx @@ -0,0 +1,172 @@ +export const metadata = { + title: "Success & Control", + description: "Why iteration success means the harness ran, not that the work is good, and why control.json is the only switch that stops the loop.", +}; + +# Success & Control + +This is the most important mental model in loopy-loop, and the one most likely to +trip you up if you skim the code. An iteration's `success` flag does **not** mean +"the work is good." It means "the assignment ran." Whether the work is actually +good is decided somewhere else entirely, by artifacts the workflow writes into the +session. This page explains that split, enumerates the ways a loop can stop, and +explains why the design deliberately keeps these two ideas apart. + +## Success means "the assignment ran" + +Every iteration ends with a normalized `result.json` that carries a boolean +`success`. It is tempting to read that field as a verdict on the task. It is not. + +`success` is `true` whenever the underlying [`team-harness`](https://github.com/writeitai/team-harness) +run returns normally. It is `false` only when the harness itself raises — a +configuration error, a `TeamHarnessError`, or an unexpected exception. Per-worker +exit codes and statuses are intentionally **not** consulted when deciding iteration +success. + +```json +{ + "success": true, + "text": "final response", + "error": null, + "harness_run_id": "run-123", + "harness_output_dir": ".loopy_loop/sessions//harness_outputs/0001_planner/run-123" +} +``` + +A `success: true` here tells you the assignment completed a full harness run. It +tells you nothing about whether the login page works, the tests pass, or the docs +got written. + +### Why not trust worker exit codes? + +team-harness's coordinator is an orchestrator, not a build system. It can +legitimately return a normal result *after* a worker has failed: it might +synthesize a final answer, decide it already has enough information, or route +around a dead worker. So worker exit codes are a noisy proxy for "did the +assignment succeed": + +- A non-zero worker can accompany a perfectly good outcome. +- An all-green set of workers can accompany a useless one. + +Mapping those signals to a single boolean would manufacture false precision. +loopy-loop instead draws the line at the only thing it can observe reliably — *did +the assignment run to completion without the harness itself erroring* — and pushes +the "was it any good" question to an explicit evaluation layer. See +[Evaluation](/docs/evaluation) for how that layer works. + +One consequence worth knowing: the scheduler keys workflow cadence +(`run_every`, `must_follow`, `run_after_successes`) off this mechanical success. +An iteration where a worker actually failed but the harness returned normally still +advances those counters. This is an accepted, bounded inaccuracy — the worst case +is a slightly-off cadence, never a false "goal met," because completion is gated by +`control.json`, not by `success`. + +## control.json is the only stop switch + +There is exactly one thing that stops the loop: the session-scoped `control.json` +file. It starts in the `running` state when the session is created: + +```json +{ + "state": "running", + "reason": "session active", + "stop_reason": null, + "schema_version": 1 +} +``` + +A workflow stops the loop by writing `state: "stopped"` with a `stop_reason`. To +stop because the goal is genuinely met: + +```json +{ + "state": "stopped", + "reason": "Accepted evidence satisfies the goal.", + "stop_reason": "goal_met", + "schema_version": 1 +} +``` + +To stop because the loop hit a terminal blocker it cannot work around: + +```json +{ + "state": "stopped", + "reason": "No usable remote and the task requires a merged PR.", + "stop_reason": "unresolvable_error", + "schema_version": 1 +} +``` + +The coordinator reads `control.json` **only** from the session directory, and only +after an iteration finishes. When a workflow writes those two values, it is making +an explicit, auditable, reversible stop decision. That is the point: a human or an +agent chose to stop, and you can see exactly why. + +## goal_check.json is evidence, not control + +The other file people expect to stop the loop is `goal_check.json`. It does not. + +`goal_check.json` is a per-iteration eval artifact written by the reserved +`goal_check` workflow (or any workflow configured with `emits_goal_check: true`). +It records whether the goal looks met, with a reason: + +```json +{"goal_met": false, "reason": "CTA exists, but deployment docs are still missing.", "schema_version": 1} +``` + +A valid `goal_check.json` is **evidence only**. Even a `goal_met: true` does not +stop the loop by itself. Something still has to make the explicit stop decision by +updating `control.json`. In the packaged `inner_outer_eval` set, that is the job of +the workflow that reviews the evidence. Keeping evidence (`goal_check.json`) and +control (`control.json`) separate means the two can never be silently conflated: +producing evidence is not the same as deciding to stop. + +## The full set of stop reasons + +When the loop stops, the coordinator records a `stop_reason`. Two of these are +written by a workflow into `control.json`; the rest are derived by the coordinator +itself. + +| `stop_reason` | Source | Meaning | +|---|---|---| +| `goal_met` | `control.json` | A workflow accepted the evidence and stopped the loop successfully. | +| `unresolvable_error` | `control.json` | A workflow hit a terminal blocker and stopped the loop deliberately. | +| `goal_check_broken` | Coordinator | `goal_check.json` was missing or invalid for too many iterations in a row (see below). | +| `max_turns` | Coordinator | The completed-iteration count reached `max_turns`. | +| `stop_requested` | Coordinator | A human ran `loopy stop`. | +| `no_eligible_workflow` | Coordinator | No workflow was eligible to run next. | +| `invalid_control_output` | Coordinator | `control.json` existed but could not be parsed as a valid control signal. | + +Only `goal_met` and `unresolvable_error` are valid values for a workflow to write +into `control.json`. The coordinator handles the rest. + +### goal_check_broken + +Because completion ultimately rests on the eval layer, that layer has to actually +run and produce valid output. If a `goal_check`-emitting workflow repeatedly fails +to write a valid `goal_check.json`, the coordinator counts the consecutive +failures. Once they reach `goal_check_consecutive_failures_cap` (default `3`), it +stops the loop with `stop_reason: "goal_check_broken"` rather than looping +forever on a broken evaluator. A single valid `goal_check.json` resets the counter. + +## Why this split exists + +Both halves of this page follow one principle: **do not infer semantic success +from noisy mechanical signals; push the success and stop decisions to an explicit, +purpose-built layer.** + +- Mechanical `success` is honest about what it can observe — the harness ran — and + nothing more. +- `goal_check.json` records a judgment about the outcome, as evidence. +- `control.json` is the single, explicit, auditable gate that actually stops the + loop. + +This is original design intent, not an accident to be "fixed." If you find yourself +wanting worker exit codes to decide success, or `goal_check.json` to stop the loop, +re-read this page first — those shortcuts reintroduce exactly the false precision +the design avoids. + +For how the evidence itself is produced and judged, see +[Evaluation](/docs/evaluation). For where every one of these files lives on disk, +see [Session Layout](/docs/session-layout). diff --git a/website/src/app/docs/troubleshooting/page.mdx b/website/src/app/docs/troubleshooting/page.mdx new file mode 100644 index 0000000..e39ed95 --- /dev/null +++ b/website/src/app/docs/troubleshooting/page.mdx @@ -0,0 +1,186 @@ +export const metadata = { + title: "Troubleshooting", + description: "Symptom, cause, and fix for the common loopy-loop problems: loops that won't stop, goal_check failures, crash recovery and resume, and connectivity.", +}; + +# Troubleshooting + +Most loopy-loop problems fall into a handful of categories: a loop that will not +stop, a broken evaluator, a coordinator that refuses to start, or a worker that +cannot reach it. This page is organized as symptom, cause, and fix so you can jump +to the one you are hitting. When a fix depends on a deeper contract, it links to the +page that explains it. + +## The loop will not stop + +**Symptom.** Evals look like they pass, or the goal clearly looks met, but the loop +keeps assigning new iterations. + +**Cause.** `goal_check.json` does not stop the loop. It is per-iteration evidence +only. The single switch that stops a run is the session-scoped `control.json`. + +**Fix.** Make sure a workflow actually writes `state: "stopped"` with a valid +`stop_reason` (`goal_met` or `unresolvable_error`) to `control.json`: + +```json +{ + "state": "stopped", + "reason": "Accepted evidence satisfies the goal.", + "stop_reason": "goal_met", + "schema_version": 1 +} +``` + +If you expected an eval workflow to stop the loop, confirm three things: it has +`emits_goal_check: true`, it writes valid JSON to the exact `goal_check.json` +output path, **and** it updates `control.json` when the goal is met. The eval verdict +is evidence; the stop is a separate, explicit decision. See +[Success & Control](/docs/success-and-control) for the full model. + +You can always force a stop by hand with `loopy stop`, which the loop records as +`stop_reason: "stop_requested"`. + +## The loop stopped with goal_check_broken + +**Symptom.** The run ended early with `stop_reason: "goal_check_broken"`. + +**Cause.** A `goal_check`-emitting workflow repeatedly failed to write a valid +`goal_check.json`. The coordinator counts consecutive failures and, once they reach +`goal_check_consecutive_failures_cap` (default `3`), stops rather than looping on a +broken evaluator. + +**Fix.** Open the recent `iterations/_/` directories and check +whether `goal_check.json` is missing or malformed. The required v1 schema is: + +```json +{ + "goal_met": false, + "reason": "CTA exists, but deployment docs are still missing.", + "schema_version": 1 +} +``` + +Fix the workflow prompt so it writes valid JSON to the correct path every run. A +single valid `goal_check.json` resets the failure counter. See +[Evaluation](/docs/evaluation) for how the eval workflows should produce it. + +## The loop stopped with invalid_control_output + +**Symptom.** The run ended with `stop_reason: "invalid_control_output"`. + +**Cause.** `control.json` existed but could not be parsed as a valid control signal +— malformed JSON, a wrong `schema_version`, or missing fields. + +**Fix.** Restore `control.json` to a valid shape. While the loop is meant to keep +running it should read: + +```json +{ + "state": "running", + "reason": "session active", + "stop_reason": null, + "schema_version": 1 +} +``` + +Make sure the workflow that writes `control.json` emits exactly this schema and only +ever sets `stop_reason` to `goal_met` or `unresolvable_error`. + +## Coordinator refuses to start (preflight fails) + +The coordinator runs a preflight check before it will start. Several distinct errors +surface here: + +- **API key not exported.** Export the environment variable named in + `team_harness_api_key_env` (often `OPENROUTER_API_KEY`) in **both** the coordinator + shell and every worker shell. Some providers, such as `codex`, skip the API-key + check and use local auth instead. +- **Unknown config field.** The config parser rejects unknown keys. Every + `team_harness_*` field name is exact — check spelling against + [Configuration](/docs/configuration). +- **Workflow id collides with `goal_check`.** `goal_check` is reserved. Pick a + different id for your own workflow. +- **`must_follow` references a missing workflow.** The referenced id must match a + folder under `.loopy_loop/workflow_sets//workflows/`. +- **`run_after_successes.workflow_id` references a missing workflow.** Same rule — + the id must match an existing workflow folder. + +## Re-running the coordinator against a running session + +**Symptom.** Starting the coordinator fails with a message about existing running +state. + +**Cause.** This is intentional. If the previous coordinator left the loop in a +non-terminal (`running`) state — for example because it was killed without reaching +a stop — starting fresh over it could corrupt the run. + +**Fix.** Reattach to the live session instead of starting over: + +```bash +loopy coordinator --host 127.0.0.1 --port 8080 --resume +``` + +If you would rather end that session cleanly first, run `loopy stop` to drive it to +a terminal state, then start a fresh coordinator without `--resume`. On startup, a +coordinator archives an already-terminal state and starts fresh; a non-terminal +state requires `--resume`. See [CLI Reference](/docs/cli-reference) for the flags. + +## Killing only the coordinator leaves state "running" + +**Symptom.** You stopped the coordinator process, and now `loopy status` still shows +the session as running. + +**Cause.** Killing the coordinator does not write a terminal state; the session +stays `running` on disk. + +**Fix.** Either pass `--resume` the next time you start the coordinator to reattach, +or run `loopy stop` first to reach a terminal state before a fresh start. + +## A worker crashed mid-iteration + +**Symptom.** A worker process died in the middle of an assignment, and you are not +sure whether that iteration's work was lost or will be double-counted. + +**Cause.** The worker crashed inside the handoff window — after writing its result +but before the coordinator acknowledged `/finished`. + +**Fix.** Usually nothing. The loop is built to recover: when the next `/register` +arrives, the coordinator looks in the current iteration directory for +`pending_finished_request.json` (or, failing that, `result.json`). If either proves +the task completed, it records the completed result instead of marking the task +`abandoned`. Only a task with no recoverable local result is recorded as failed with +`error="abandoned"`. Just start a worker again and let it re-register. The +[HTTP Contract](/docs/http-contract) documents the recovery rules in full. + +## The worker cannot reach the coordinator + +**Symptom.** The worker errors on connect, or nothing happens after you start it. + +**Cause.** The worker's `--coordinator` URL is wrong, the coordinator is not +listening on that host/port, or the two processes do not share the environment they +need. + +**Fix.** Confirm the coordinator is up on the host and port you started it with, and +point the worker at the same address: + +```bash +loopy worker --coordinator http://127.0.0.1:8080 +``` + +Also export the `team_harness_api_key_env` variable in the worker shell, not just +the coordinator shell — each process resolves the key independently. The worker +calls `/register` once for its first task, then loops on `/finished` until it +receives `action: "stop"`; multiple workers may share one coordinator. + +## A workflow ignores project_state + +**Symptom.** A workflow does not read or update the `project_state/` files you +expected it to. + +**Cause.** The runtime only injects the session paths into the rendered prompt; it +does not parse markdown state or enforce any ownership rules. + +**Fix.** State the contract explicitly in the workflow prompt — which +`project_state/` files to read, which to update, and who owns each. See +[Session Layout](/docs/session-layout) for the conventional file set and ownership +rules that reusable workflows follow. diff --git a/website/src/app/docs/workflows/page.mdx b/website/src/app/docs/workflows/page.mdx new file mode 100644 index 0000000..65fe18a --- /dev/null +++ b/website/src/app/docs/workflows/page.mdx @@ -0,0 +1,126 @@ +export const metadata = { + title: "Workflows", + description: + "Workflow sets and their folder layout, per-workflow scheduling fields, the reserved goal_check workflow, and the three shipped templates.", +}; + +# Workflows + +A workflow is a single named unit of work the loop can run: a prompt plus a small scheduling config. Workflows are grouped into **workflow sets**, and the coordinator picks one eligible workflow to run each iteration. This page covers how workflows are laid out on disk, how their scheduling fields decide what runs next, and the three templates that ship with loopy-loop. + +## Workflow sets and folder layout + +Workflow sets are mandatory — even a single-loop repo uses one. A set is a directory of workflows under `.loopy_loop/workflow_sets/`, and each workflow is a folder holding exactly two files: + +```text +.loopy_loop/workflow_sets//workflows// +├── config.yaml +└── prompt.txt +``` + +The workflow id is the folder name. `prompt.txt` is the prompt the workflow runs — the coordinator renders it with the concrete session paths before handing it to a worker. `config.yaml` holds the scheduling fields described below. For example, the recommended template lays out four workflows like this: + +```text +.loopy_loop/workflow_sets/inner_outer_eval/workflows/ +├── outer/ +├── inner/ +├── eval_reviewer/ +└── eval_runner/ +``` + +Your `loopy_loop_config.yaml` selects the default set with `workflow_set`, and `loopy coordinator --workflow-set NAME` can override it for a single session. Workflow definitions are part of your repo and should be committed. + +The older flat `.loopy_loop/workflows/...` layout is **not** loaded at runtime; the coordinator will point you at the `workflow_sets/` location if it finds workflows in the legacy spot. + +## Scheduling fields + +Each `config.yaml` describes when its workflow is eligible to run. A full config looks like this: + +```yaml +enabled: true +priority: 0 +run_every: 1 +must_follow: null +not_before_iteration: 0 +run_on_start: false +run_after_successes: null +emits_goal_check: false +description: "" +``` + +Each turn, the coordinator collects every eligible workflow and picks a winner. Eligibility and ordering come from these fields: + +| Field | Type | Default | Meaning | +| --- | --- | --- | --- | +| `enabled` | boolean | `true` | Whether this workflow can be scheduled at all. | +| `priority` | integer | `0` | Tie-breaker among eligible workflows; the **higher** value runs first. | +| `run_every` | integer | `1` | Minimum number of completed iterations between runs of this workflow. Counts completed iterations, not wall-clock time. | +| `must_follow` | string or null | `null` | The workflow only becomes eligible immediately after a successful run of the named workflow. | +| `not_before_iteration` | integer | `0` | Earliest completed-iteration count at which the workflow is eligible. | +| `run_on_start` | boolean | `false` | Makes the workflow eligible before any workflow has succeeded — useful for a first-turn setup step. | +| `run_after_successes` | mapping or null | `null` | Unlocks the workflow after every N successful runs of another workflow (see below). | +| `emits_goal_check` | boolean | `false` | Declares that this workflow writes `goal_check.json`; the worker then includes that output path in the prompt. | +| `description` | string | `""` | Human-readable note about the workflow's purpose. | + +A few rules are worth calling out: + +- `must_follow` and `run_after_successes.workflow_id` must reference workflow ids that exist in the same set. A dangling reference fails the coordinator's startup preflight. +- `run_every` is based on completed iteration count, so `run_every: 3` means "at most once every three completed iterations," not "every three minutes." +- `priority` only decides ties among workflows that are *already* eligible — it never makes an ineligible workflow run. + +### run_after_successes + +`run_after_successes` schedules a workflow on the cadence of another workflow's successes. This is how the eval workflows fire periodically rather than every turn: + +```yaml +run_after_successes: + workflow_id: inner + every: 10 +``` + +Here the workflow becomes eligible again after every 10 successful `inner` runs. Combined with `run_on_start: true`, it can also run once at the very beginning before any `inner` success exists yet. + +## The reserved goal_check workflow + +`goal_check` is a reserved workflow id. The `default` template scaffolds exactly this one workflow, and it plays a specific role: it evaluates whether the repo already satisfies the goal and writes the per-iteration `goal_check.json` eval artifact. Don't rename or delete it in a set that relies on it. + +Two behaviors are special: + +- **It waits for real work.** `goal_check` is not eligible until at least one non-`goal_check` workflow has succeeded, so it never runs against an untouched repo. +- **Other workflows can emit goal checks too.** Setting `emits_goal_check: true` on any workflow tells the worker to include a `goal_check.json` output path in that workflow's prompt, letting it write the same eval artifact. In the `inner_outer_eval` template, `eval_runner` uses this instead of a standalone `goal_check` workflow. + +Writing `goal_check.json` is evidence, not a stop signal. Stopping the loop always requires updating the session's `control.json`. See [Success & Control](/docs/success-and-control) for the stop contract and [Evaluation](/docs/evaluation) for how eval checks are produced and consumed. + +## The shipped templates + +`loopy init --template NAME` scaffolds one of three workflow sets. Pick based on how much structure your goal needs. + +### default + +The minimal starting point. It creates only the reserved `goal_check` workflow in a set named `main`. Choose this when you want to design your own workflows from a clean slate and only need the built-in goal check to begin with. + +### inner_outer_eval + +The recommended template for most real work. It creates four workflows that form a plan → implement → evaluate rhythm: + +- `outer` — reviews durable project state, updates the high-level plan, and prepares the next available leaf task. +- `inner` — implements the selected leaf task in the target repo and marks it waiting for review (`must_follow: outer`). +- `eval_reviewer` — creates or refreshes session-scoped [eval-banana](/docs/evaluation) checks (`run_on_start: true`, then after every 10 `inner` successes). +- `eval_runner` — runs those checks and writes `goal_check.json` (`must_follow: eval_reviewer`, `emits_goal_check: true`). + +Choose this when you have a substantial goal with observable completion criteria and want planning, implementation, and evaluation cleanly separated. + +### pm_planner_dispatcher + +A PM-orchestration set for breaking a large effort into child sessions. Its two workflows coordinate work rather than implementing it directly: + +- `planner` — maintains PM state, selects one work item, and reviews terminal child-session evidence (`run_on_start: true`). +- `dispatcher` — writes one child-session request for the selected item, or imports finished child evidence back into PM state (`must_follow: planner`). + +Choose this when the goal is really a portfolio of separable sub-goals, each of which deserves its own focused session. See [Child Sessions](/docs/child-sessions) for how the parent spawns and resumes children. + +## Where to go next + +- [Success & Control](/docs/success-and-control) — how workflows stop the loop. +- [Evaluation](/docs/evaluation) — writing and running session-scoped eval checks. +- [Configuration](/docs/configuration) — the root config that selects a workflow set. diff --git a/website/src/app/globals.css b/website/src/app/globals.css new file mode 100644 index 0000000..f5596c7 --- /dev/null +++ b/website/src/app/globals.css @@ -0,0 +1,166 @@ +@import "tailwindcss"; + +@plugin "@tailwindcss/typography"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-brand: var(--brand); + --color-gold: var(--gold); + --color-gold-foreground: var(--gold-foreground); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --font-sans: var(--font-hanken), ui-sans-serif, system-ui, sans-serif; +} + +/* + * Brand palette lifted from writeit.ai (writeit/fe/styles/globals.scss): + * sand #f7ebbd · ink #222433 · green #5ca493 · gold #ebaa1a · red #f34832 + * Mapped onto shadcn-style tokens so the docs read as part of the WriteIt family. + */ +:root { + --background: #f7ebbd; /* sand */ + --foreground: #222433; /* ink */ + --card: #fdf8e6; + --card-foreground: #222433; + --popover: #fdf8e6; + --popover-foreground: #222433; + /* Text-bearing green, darkened from the brand green to meet WCAG AA on sand + (4.57:1) and with white (5.46:1). Used for links and primary buttons. */ + --primary: #2f7563; + --primary-foreground: #ffffff; + --secondary: #efe1a8; + --secondary-foreground: #222433; + --muted: #efe1a8; + --muted-foreground: #59583f; + --accent: #ecdf9c; + --accent-foreground: #222433; + --destructive: #d63a25; /* red, darkened for AA with white text */ + --destructive-foreground: #ffffff; + --border: #e0d29a; + --input: #e0d29a; + --ring: #5ca493; + /* Vivid writeit brand green — decorative only (logo/focus accents), never text. */ + --brand: #5ca493; + --gold: #ebaa1a; /* gold */ + --gold-foreground: #222433; + --radius: 0.5rem; +} + +/* Dormant dark palette — wired up when a theme toggle is added later. */ +.dark { + --background: #1b1c24; + --foreground: #f3ecd2; + --card: #24252f; + --card-foreground: #f3ecd2; + --popover: #24252f; + --popover-foreground: #f3ecd2; + --primary: #6cb7a4; + --primary-foreground: #14231f; + --secondary: #2a2b36; + --secondary-foreground: #f3ecd2; + --muted: #2a2b36; + --muted-foreground: #a8a58c; + --accent: #2f3340; + --accent-foreground: #f3ecd2; + --destructive: #f3654f; + --destructive-foreground: #14231f; + --border: #34353f; + --input: #34353f; + --ring: #6cb7a4; + --brand: #6cb7a4; + --gold: #ebaa1a; + --gold-foreground: #14231f; +} + +@utility container { + margin-inline: auto; + padding-inline: 1.5rem; + max-width: 1400px; +} + +@media (prefers-reduced-motion: no-preference) { + html { + scroll-behavior: smooth; + } +} + +@layer base { + * { + border-color: var(--border); + } + body { + background-color: var(--background); + color: var(--foreground); + } +} + +/* On-brand typography for the rendered MDX. */ +.prose { + --tw-prose-body: var(--foreground); + --tw-prose-headings: var(--foreground); + --tw-prose-lead: var(--muted-foreground); + --tw-prose-links: var(--primary); + --tw-prose-bold: var(--foreground); + --tw-prose-counters: var(--muted-foreground); + --tw-prose-bullets: var(--primary); + --tw-prose-hr: var(--border); + --tw-prose-quotes: var(--foreground); + --tw-prose-quote-borders: var(--primary); + --tw-prose-captions: var(--muted-foreground); + --tw-prose-code: var(--foreground); + --tw-prose-pre-code: #222433; + --tw-prose-pre-bg: #fbf4d6; + --tw-prose-th-borders: var(--border); + --tw-prose-td-borders: var(--border); +} + +/* Inline code (not fenced blocks). */ +.prose :where(code):not(pre code) { + background-color: var(--secondary); + padding: 0.15em 0.4em; + border-radius: var(--radius-sm); + font-weight: 500; + font-size: 0.875em; +} +.prose :where(code):not(pre code)::before, +.prose :where(code):not(pre code)::after { + content: ""; +} + +/* Fenced code blocks (rehype-pretty-code, keepBackground: false). */ +.prose pre { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 1rem 1.25rem; + overflow-x: auto; +} +.prose pre code { + background-color: transparent; + padding: 0; + font-weight: 400; +} +[data-rehype-pretty-code-figure] { + margin-top: 1.25em; + margin-bottom: 1.25em; +} diff --git a/website/src/app/layout.tsx b/website/src/app/layout.tsx new file mode 100644 index 0000000..440b99f --- /dev/null +++ b/website/src/app/layout.tsx @@ -0,0 +1,46 @@ +import type { Metadata } from "next"; +import { Hanken_Grotesk } from "next/font/google"; +import "./globals.css"; +import { SiteHeader } from "@/components/site/SiteHeader"; + +// Open-font stand-in for writeit.ai's domain-locked proxima-nova. Self-hosted +// by next/font so the site stays a self-contained module. +const hanken = Hanken_Grotesk({ + subsets: ["latin"], + variable: "--font-hanken", +}); + +const siteUrl = "https://loopy.writeit.ai"; + +export const metadata: Metadata = { + metadataBase: new URL(siteUrl), + title: { + default: "loopy-loop — Documentation", + template: "%s — loopy-loop", + }, + description: + "Run long-running AI agent workflows inside your repository. Documentation for loopy-loop.", + openGraph: { + title: "loopy-loop — Documentation", + description: + "Run long-running AI agent workflows inside your repository.", + url: siteUrl, + siteName: "loopy-loop", + type: "website", + }, +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + +
{children}
+ + + ); +} diff --git a/website/src/app/page.tsx b/website/src/app/page.tsx new file mode 100644 index 0000000..0ae6d6a --- /dev/null +++ b/website/src/app/page.tsx @@ -0,0 +1,49 @@ +import Link from "next/link"; +import { ArrowRight, Github } from "lucide-react"; + +export default function Home() { + return ( +
+ + Open source · Apache-2.0 + + +

+ Run long-running AI agent workflows inside your repository. +

+ +

+ loopy-loop turns a + goal into an inspectable, resumable loop of agent iterations — plan, + implement, evaluate, record evidence, repeat — that runs until the goal is + met. State lives in git and files, never hidden inside a chat transcript. +

+ +
+ + Read the docs + + + + + View on GitHub + +
+ +
+
+ $ + uv tool install loopy-loop +
+
+
+ ); +} diff --git a/website/src/components/docs/DocsPageNavigation.tsx b/website/src/components/docs/DocsPageNavigation.tsx new file mode 100644 index 0000000..266e9b8 --- /dev/null +++ b/website/src/components/docs/DocsPageNavigation.tsx @@ -0,0 +1,42 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { findAdjacentPages } from "@/lib/docs/navigation"; + +export function DocsPageNavigation() { + const pathname = usePathname(); + const { prev, next } = findAdjacentPages(pathname); + + if (!prev && !next) { + return null; + } + + return ( + + ); +} diff --git a/website/src/components/docs/DocsSidebar.tsx b/website/src/components/docs/DocsSidebar.tsx new file mode 100644 index 0000000..1e8abde --- /dev/null +++ b/website/src/components/docs/DocsSidebar.tsx @@ -0,0 +1,62 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { docsNavigation, type NavItem } from "@/lib/docs/navigation"; + +function isActive(pathname: string, href: string) { + const norm = (p: string) => (p.length > 1 ? p.replace(/\/$/, "") : p); + return norm(pathname) === norm(href); +} + +function NavLink({ item }: { item: NavItem }) { + const pathname = usePathname(); + const active = isActive(pathname, item.href); + + return ( + + {item.title} + + ); +} + +function NavSection({ item }: { item: NavItem }) { + return ( +
+

+ {item.title} +

+ {item.children && ( +
+ {item.children.map((child) => ( + + ))} +
+ )} +
+ ); +} + +export function DocsSidebar() { + return ( + + ); +} diff --git a/website/src/components/docs/DocsTableOfContents.tsx b/website/src/components/docs/DocsTableOfContents.tsx new file mode 100644 index 0000000..2eefeed --- /dev/null +++ b/website/src/components/docs/DocsTableOfContents.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; + +type Heading = { + id: string; + text: string; + level: number; +}; + +export function DocsTableOfContents() { + const pathname = usePathname(); + const [headings, setHeadings] = useState([]); + const [activeId, setActiveId] = useState(""); + + useEffect(() => { + const elements = document.querySelectorAll("article h2[id], article h3[id]"); + const items: Heading[] = Array.from(elements).map((el) => ({ + id: el.id, + text: el.textContent ?? "", + level: el.tagName === "H2" ? 2 : 3, + })); + setHeadings(items); + setActiveId(""); + }, [pathname]); + + useEffect(() => { + if (headings.length === 0) return; + + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + } + }, + { rootMargin: "0px 0px -80% 0px", threshold: 0.1 } + ); + + const elements = headings + .map((h) => document.getElementById(h.id)) + .filter(Boolean) as HTMLElement[]; + + for (const el of elements) { + observer.observe(el); + } + + return () => observer.disconnect(); + }, [headings]); + + if (headings.length === 0) { + return null; + } + + return ( + + ); +} diff --git a/website/src/components/docs/MobileDocsSidebar.tsx b/website/src/components/docs/MobileDocsSidebar.tsx new file mode 100644 index 0000000..3a5289d --- /dev/null +++ b/website/src/components/docs/MobileDocsSidebar.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; +import * as Dialog from "@radix-ui/react-dialog"; +import { Menu, X } from "lucide-react"; +import { DocsSidebar } from "./DocsSidebar"; + +export function MobileDocsSidebar() { + const [isOpen, setIsOpen] = useState(false); + const pathname = usePathname(); + + // Close the drawer on navigation. + useEffect(() => { + setIsOpen(false); + }, [pathname]); + + // Radix Dialog handles focus trapping, focus restoration, scroll lock, + // Escape-to-close, and role/aria-modal semantics. + return ( + + + + + + + +
+ + Documentation + + + + +
+ +
+
+
+ ); +} diff --git a/website/src/components/search/SearchCommand.tsx b/website/src/components/search/SearchCommand.tsx new file mode 100644 index 0000000..8ec6fcc --- /dev/null +++ b/website/src/components/search/SearchCommand.tsx @@ -0,0 +1,181 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import { useRouter } from "next/navigation"; +import { Command } from "cmdk"; +import { FileText, Search } from "lucide-react"; +import { searchDocs, type SearchOutcome } from "@/lib/search/pagefind"; + +const EMPTY: SearchOutcome = { status: "empty", results: [] }; + +export function SearchCommand() { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(""); + const [outcome, setOutcome] = useState(EMPTY); + const [loading, setLoading] = useState(false); + const [shortcut, setShortcut] = useState("⌘K"); + const debounceRef = useRef | null>(null); + // Monotonic id so an older, slower search can't overwrite a newer one. + const requestIdRef = useRef(0); + + // Show the platform-correct shortcut hint after mount (avoids hydration drift). + useEffect(() => { + const isMac = + typeof navigator !== "undefined" && + /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent); + if (!isMac) setShortcut("Ctrl K"); + }, []); + + // Cmd/Ctrl+K toggles the palette from anywhere on the site. + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key.toLowerCase() === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((o) => !o); + } + }; + document.addEventListener("keydown", onKey); + return () => document.removeEventListener("keydown", onKey); + }, []); + + // Debounced Pagefind query, race-safe against out-of-order resolutions. + useEffect(() => { + if (debounceRef.current) clearTimeout(debounceRef.current); + const q = query.trim(); + if (!q) { + setOutcome(EMPTY); + setLoading(false); + return; + } + const requestId = ++requestIdRef.current; + setLoading(true); + debounceRef.current = setTimeout(() => { + searchDocs(q).then((res) => { + // Ignore if a newer query superseded this one. + if (requestId !== requestIdRef.current) return; + setOutcome(res); + setLoading(false); + }); + }, 150); + return () => { + if (debounceRef.current) clearTimeout(debounceRef.current); + }; + }, [query]); + + // Reset transient state whenever the dialog closes; invalidate in-flight work. + useEffect(() => { + if (!open) { + requestIdRef.current++; + setQuery(""); + setOutcome(EMPTY); + setLoading(false); + } + }, [open]); + + const onSelect = useCallback( + (url: string) => { + setOpen(false); + router.push(url); + }, + [router] + ); + + const { status, results } = outcome; + + return ( + <> + + + +
+ + +
+ + {loading && ( +
+ Searching... +
+ )} + {!loading && status === "empty" && ( +
+ Type to search the documentation. +
+ )} + {!loading && status === "ok" && results.length === 0 && ( + + No results found. + + )} + {!loading && status === "unavailable" && ( +
+ Search isn't available yet. It works on the deployed site. +
+ )} + {!loading && status === "error" && ( +
+ Something went wrong. Try a different search. +
+ )} + {results.map((result) => ( + onSelect(result.url)} + className="flex cursor-pointer flex-col gap-1 rounded-md px-3 py-2 text-sm data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground" + > + + + {result.title} + + + + ))} +
+
+ + + ↵ + {" "} + to open + + + + esc + {" "} + to close + +
+
+ + ); +} diff --git a/website/src/components/site/SiteHeader.tsx b/website/src/components/site/SiteHeader.tsx new file mode 100644 index 0000000..062fc13 --- /dev/null +++ b/website/src/components/site/SiteHeader.tsx @@ -0,0 +1,38 @@ +import Link from "next/link"; +import { Github } from "lucide-react"; +import { SearchCommand } from "@/components/search/SearchCommand"; + +export function SiteHeader() { + return ( +
+
+ + + loopy-loop + + +
+ + + Docs + + + + +
+
+
+ ); +} diff --git a/website/src/lib/docs/navigation.ts b/website/src/lib/docs/navigation.ts new file mode 100644 index 0000000..7862ac9 --- /dev/null +++ b/website/src/lib/docs/navigation.ts @@ -0,0 +1,64 @@ +export type NavItem = { + title: string; + href: string; + children?: NavItem[]; +}; + +// Single source of truth for the docs sidebar order and grouping. It also +// drives prev/next pagination. Add a page here when you add its page.mdx. +export const docsNavigation: NavItem[] = [ + { title: "Introduction", href: "/docs" }, + { title: "Getting Started", href: "/docs/getting-started" }, + { title: "Concepts", href: "/docs/concepts" }, + { title: "Configuration", href: "/docs/configuration" }, + { title: "Workflows", href: "/docs/workflows" }, + { title: "Success & Control", href: "/docs/success-and-control" }, + { title: "Evaluation", href: "/docs/evaluation" }, + { title: "Child Sessions", href: "/docs/child-sessions" }, + { + title: "Reference", + href: "/docs/session-layout", + children: [ + { title: "Session Layout", href: "/docs/session-layout" }, + { title: "HTTP Contract", href: "/docs/http-contract" }, + { title: "CLI Reference", href: "/docs/cli-reference" }, + ], + }, + { title: "Troubleshooting", href: "/docs/troubleshooting" }, +]; + +export function flattenNavigation(items: NavItem[]): NavItem[] { + const result: NavItem[] = []; + for (const item of items) { + result.push(item); + if (item.children) { + result.push(...flattenNavigation(item.children)); + } + } + return result; +} + +export function findAdjacentPages(pathname: string): { + prev: NavItem | null; + next: NavItem | null; +} { + // De-duplicate on href so a section header that points at its first child + // (e.g. "Reference" -> Session Layout) does not create a self-adjacency. + const seen = new Set(); + const flat = flattenNavigation(docsNavigation).filter((item) => { + if (seen.has(item.href)) return false; + seen.add(item.href); + return true; + }); + + const normalize = (p: string) => (p.length > 1 ? p.replace(/\/$/, "") : p); + const target = normalize(pathname); + const index = flat.findIndex((item) => normalize(item.href) === target); + if (index === -1) { + return { prev: null, next: null }; + } + return { + prev: index > 0 ? flat[index - 1] : null, + next: index < flat.length - 1 ? flat[index + 1] : null, + }; +} diff --git a/website/src/lib/search/pagefind.ts b/website/src/lib/search/pagefind.ts new file mode 100644 index 0000000..1a5af07 --- /dev/null +++ b/website/src/lib/search/pagefind.ts @@ -0,0 +1,86 @@ +// Thin client-side wrapper around Pagefind. +// +// Pagefind indexes the static export at build time (see `postbuild` in +// package.json) and writes a small JS API to `/pagefind/pagefind.js`. That file +// only exists in the deployed `out/` build — not during `next dev` — so we load +// it lazily and indirectly, keeping the bundler from trying to resolve it. + +export type PagefindResult = { + url: string; + title: string; + excerpt: string; +}; + +/** Outcome of a search, so the UI can distinguish "no hits" from "unavailable". */ +export type SearchOutcome = { + status: "empty" | "ok" | "unavailable" | "error"; + results: PagefindResult[]; +}; + +type PagefindDocument = { + url: string; + meta?: { title?: string }; + excerpt: string; +}; + +type PagefindApi = { + init?: () => Promise; + search: (query: string) => Promise<{ + results: Array<{ id: string; data: () => Promise }>; + }>; +}; + +// `new Function` keeps webpack/Turbopack from statically analysing the import. +const dynamicImport = new Function( + "url", + "return import(url)" +) as (url: string) => Promise; + +let pagefindPromise: Promise | null = null; + +function importPagefind(): Promise { + // Build a fully-qualified URL from the current origin so the dynamic import + // never resolves against an ambiguous base URL. + const url = new URL("/pagefind/pagefind.js", window.location.href).href; + return dynamicImport(url) + .then(async (mod) => { + if (mod.init) await mod.init(); + return mod; + }) + .catch(() => { + // Don't cache the failure permanently — allow a later retry (e.g. the + // index finished deploying, or a transient network error). + pagefindPromise = null; + return null; + }); +} + +/** Load (once) the Pagefind API, or null if the index is currently unavailable. */ +export function loadPagefind(): Promise { + if (!pagefindPromise) { + pagefindPromise = importPagefind(); + } + return pagefindPromise; +} + +/** Run a search. Never rejects — failures are reported via `status`. */ +export async function searchDocs(query: string): Promise { + if (!query.trim()) return { status: "empty", results: [] }; + + const pagefind = await loadPagefind(); + if (!pagefind) return { status: "unavailable", results: [] }; + + try { + const search = await pagefind.search(query); + const top = search.results.slice(0, 8); + const docs = await Promise.all(top.map((r) => r.data())); + const results = docs.map((d) => ({ + url: d.url, + title: d.meta?.title ?? d.url, + excerpt: d.excerpt, + })); + return { status: "ok", results }; + } catch { + return { status: "error", results: [] }; + } +} diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts new file mode 100644 index 0000000..9e6db54 --- /dev/null +++ b/website/src/lib/utils.ts @@ -0,0 +1,7 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +/** Merge Tailwind CSS class names, resolving conflicts via tailwind-merge. */ +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 0000000..0628801 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "es2017", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "**/*.mdx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +}