diff --git a/AGENTS.md b/AGENTS.md index 424d926..c485edb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,6 +23,11 @@ tiny (≤ 200 lines). `CLAUDE.md` in each folder is a symlink to its `AGENTS.md` - **Ovens are declarative and non-executable** — data, never code. No `eval`, no runtime component/renderer injection, no imported UI. +## Agent integrations +- Skills (`burnlist install`) and Streaming Diff hooks (`burnlist hooks install`) are + independent. Keep their docs accurate and separate; see `README.md` and + `skills/burnlist/references/installation.md` before changing either surface. + ## Hygiene - **Conventional commits** (`feat:`/`fix:`/…); reference a burnlist item id when one applies: `feat: … (auth-07)`. diff --git a/README.md b/README.md index 4d4c3fe..5e87325 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Burnlist requires Node.js 18 or newer. npm install --global burnlist ``` -The global package installs the `burnlist` command and registers one bundled agent skill under `$HOME/.agents/skills`. +The global package installs the `burnlist` command and registers the bundled Burnlist skill for Claude Code under `~/.claude/skills` and Codex under `~/.agents/skills`. Streaming Diff hooks are a separate, opt-in per-repository integration; see [Agent integrations](#agent-integrations). Ask your agent to create a Burnlist for a goal or continue an existing one. The skill owns that workflow; the CLI provides the dashboard and protocol helpers. @@ -70,11 +70,75 @@ Projects that need worker orchestration can import `createDifferentialTestingWor See the [Differential Testing data contract](skills/burnlist/references/differential-testing-data.md) and [adapter SDK reference](skills/burnlist/references/differential-testing-adapter-sdk.md) for scenario bundles, exact sessions, telemetry, and worker interfaces. -## Streaming Diff hooks +## Agent integrations -`burnlist hooks install --agent codex,claude` merges local Streaming Diff commands into `.codex/hooks.json` and `.claude/settings.json`; it preserves existing hook entries. The agent remains responsible for any first-run hook trust/consent prompt—Burnlist only writes configuration and never bypasses that review. `burnlist hooks status` reports whether each config is tracked (and therefore shared) or local; an already tracked config cannot be hidden with `.git/info/exclude`. +Burnlist has two independent systems. You can install the skill without hooks, hooks without the skill, or both. -Hooks use the portable `burnlist` command from `PATH`; the host resolves the platform-specific launcher. +### Skills: make Burnlist discoverable + +`burnlist install` registers the bundled skill for both Claude Code and Codex. Its default scope is the current repository and creates managed, untracked-local skill registrations via `.git/info/exclude`: + +| Agent | Per-repository target | Global target (`--global`) | +| --- | --- | --- | +| Claude Code | `/.claude/skills/burnlist` | `~/.claude/skills/burnlist` | +| Codex | `/.agents/skills/burnlist` | `~/.agents/skills/burnlist` | + +```sh +# Per-repository skill only (both agents by default) +burnlist install + +# Limit to one agent, or preview without writing +burnlist install --agent codex +burnlist install --dry-run + +# Global skill only +burnlist install --global + +# Portable per-repository copy that the team can commit +burnlist install --commit + +# Remove the matching per-repository or global registration +burnlist uninstall +burnlist uninstall --global + +# Also remove the global npm package (global scope only) +burnlist uninstall --global --purge +``` + +`--agent codex,claude` restricts either install or uninstall to the selected agents. `--commit` is per-repository only; it makes a portable copy instead of the default local registration. + +### Hooks: capture Streaming Diff edits + +`burnlist hooks install` is separate from skill installation. It is per-repository only (there is no global hooks mode) and merges Burnlist's edit-capture commands without replacing unrelated hooks: + +| Agent that consumes the hook | Worktree-root config | +| --- | --- | +| Codex | `/.codex/hooks.json` | +| Claude Code | `/.claude/settings.json` | + +Codex receives `SessionStart`, `PreToolUse`, and `PostToolUse` hooks; Claude Code also receives `PostToolUseFailure`. Edit events are limited to each agent's edit/write tools and invoke `burnlist streaming-diff hook`. Codex needs CLI version 0.124.0 or newer to run these hooks. The host needs `burnlist` on `PATH`, and the agent remains responsible for any hook trust or consent prompt. + +```sh +# Hooks only, for both agents by default +burnlist hooks install + +# Limit to one agent; --untracked requests a local exclude entry +burnlist hooks install --agent claude +burnlist hooks install --untracked + +# Inspect or remove Burnlist-managed hooks +burnlist hooks status +burnlist hooks uninstall + +# Install or remove both independent per-repository systems +burnlist install && burnlist hooks install +burnlist uninstall && burnlist hooks uninstall + +# A global skill can be combined with hooks in the current repository +burnlist install --global && burnlist hooks install +``` + +Untracked hook configs are added to `.git/info/exclude` by default; tracked configs remain shared with the team, and `--untracked` cannot hide one. `burnlist hooks uninstall` removes only Burnlist's own hook entries and preserves the rest of the config. See the [installation reference](skills/burnlist/references/installation.md) for the full CLI surface. ## Command Line @@ -82,7 +146,8 @@ Hooks use the portable `burnlist` command from `PATH`; the host resolves the pla - `burnlist --plan --digest` prints a completion digest after the active queue is empty. - `burnlist --close-completed` adds a digest when needed and moves empty in-progress Burnlists to `completed`. - `burnlist --stamp` prints a local ISO timestamp for completion records. -- `burnlist uninstall` removes the command and its registered skill. +- `burnlist install` / `burnlist uninstall` manage the independent agent-skill registrations. +- `burnlist hooks install|uninstall|status` manages the independent per-repository Streaming Diff hooks. Use `burnlist --help` for dashboard ports, scan roots, local state paths, and Oven data bindings. diff --git a/bin/burnlist.mjs b/bin/burnlist.mjs index 6aba10c..7ed20be 100755 --- a/bin/burnlist.mjs +++ b/bin/burnlist.mjs @@ -1,12 +1,14 @@ #!/usr/bin/env node -import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; -import { basename, dirname, resolve } from "node:path"; +import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { runSkillsInstallCli } from "../src/cli/skills-install-cli.mjs"; + const args = process.argv.slice(2); const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const knownSubcommands = new Set([ + "install", "uninstall", "differential-testing", "streaming-diff", @@ -24,50 +26,20 @@ const knownSubcommands = new Set([ "init", ]); -function npmGlobalPrefix() { - let current = packageRoot; - while (dirname(current) !== current) { - if (basename(current) === "node_modules") { - const parent = dirname(current); - return basename(parent) === "lib" ? dirname(parent) : parent; - } - current = dirname(current); - } - throw new Error("Burnlist is not running from a global npm installation."); -} - -function runNodeScript(path, scriptArgs) { - return spawnSync(process.execPath, [path, ...scriptArgs], { - env: process.env, - shell: false, - stdio: "inherit", - }); +function printSkillUsage(command) { + const usage = command === "install" + ? "Usage: burnlist install [--global] [--commit] [--force] [--agent codex,claude] [--dry-run]" + : "Usage: burnlist uninstall [--global] [--agent codex,claude] [--dry-run] [--purge]"; + console.log(`${usage}\n\nInstall and remove Burnlist-managed agent skills for Codex and Claude.`); } async function main() { -if (args[0] === "uninstall") { - let prefix; - try { - prefix = npmGlobalPrefix(); - } catch (error) { - console.error(error.message); - process.exit(1); - } - const unregisterPath = resolve(packageRoot, "scripts", "unregister-skills.mjs"); - const unregister = runNodeScript(unregisterPath, ["--force-global"]); - if (unregister.status !== 0) process.exit(unregister.status || 1); - - const npm = process.platform === "win32" ? "npm.cmd" : "npm"; - const removal = spawnSync(npm, ["uninstall", "--global", "--prefix", prefix, "burnlist"], { - env: process.env, - shell: false, - stdio: "inherit", - }); - if (removal.status !== 0) { - console.error("Burnlist: npm uninstall failed; restoring agent skill registrations."); - runNodeScript(resolve(packageRoot, "scripts", "register-skills.mjs"), ["--force-global"]); +if (args[0] === "install" || args[0] === "uninstall") { + if (args.includes("--help") || args.includes("-h")) { + printSkillUsage(args[0]); + return; } - process.exitCode = removal.status || 0; + process.exitCode = runSkillsInstallCli({ args, packageRoot }); return; } @@ -111,7 +83,7 @@ if (args[0] && !args[0].startsWith("--") && !["-h", "-v"].includes(args[0]) && ! process.exit(2); } -if (args[0] !== "oven" && (args.includes("--help") || args.includes("-h"))) { +if (!["oven", "hooks"].includes(args[0]) && (args.includes("--help") || args.includes("-h"))) { console.log(`Burnlist Usage: @@ -125,7 +97,7 @@ Usage: burnlist differential-testing schema burnlist differential-testing sdk burnlist streaming-diff ... - burnlist hooks [--agent codex,claude] [--untracked] + burnlist hooks [install|uninstall|status] [--agent codex,claude] [--untracked] (bare defaults to status) burnlist oven ... burnlist new [--repo ] burnlist show [#] [--repo ] @@ -137,7 +109,8 @@ Usage: burnlist unregister [path] burnlist roots [--prune] burnlist init [path] [--track] - burnlist uninstall + burnlist install [--global] [--commit] [--force] [--agent codex,claude] [--dry-run] + burnlist uninstall [--global] [--agent codex,claude] [--dry-run] [--purge] Options: --auto-port Try the next available loopback port. @@ -146,6 +119,12 @@ Options: --ovens-dir Override launch-repository custom Oven storage only. --runs-dir Override Run snapshot storage. --oven-data Bind one Oven to a read-only normalized JSON payload. + --global Install or uninstall skills in the user home directory. + --commit Per-repository install: copy portable skills for git commit. + --force Permit install to replace a Burnlist-managed portable copy with a symlink. + --agent Restrict skill install or uninstall to codex, claude, or both. + --dry-run Print skill link or portable-copy plans without writing them. + --purge With uninstall --global only, also remove the global npm package. --version, -v Print the installed Burnlist version. --help, -h Show this help.`); return; diff --git a/scripts/register-skills.mjs b/scripts/register-skills.mjs index f8baa67..9ec7ad3 100755 --- a/scripts/register-skills.mjs +++ b/scripts/register-skills.mjs @@ -1,83 +1,23 @@ #!/usr/bin/env node -import { - existsSync, - lstatSync, - mkdirSync, - readlinkSync, - readdirSync, - symlinkSync, -} from "node:fs"; -import { dirname, join, resolve } from "node:path"; +import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { registerSkills, registrationScope } from "../src/cli/skills-register.mjs"; + const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const args = new Set(process.argv.slice(2)); -const dryRun = args.has("--dry-run"); -const globalInstall = process.env.npm_config_global === "true" || args.has("--force-global"); +const args = process.argv.slice(2); -if (!globalInstall) { +if (process.env.npm_lifecycle_event === "postinstall" && process.env.npm_config_global !== "true" && args.length === 0) { console.log("Burnlist: local npm install detected; agent skill registration is only performed for global installs."); - process.exit(0); -} - -const home = process.env.HOME || process.env.USERPROFILE; -if (!home) { - console.error("Burnlist: cannot register agent skills because no user home directory is available."); - process.exit(1); -} - -const sourceRoot = resolve(packageRoot, "skills"); -const targetRoot = resolve(process.env.BURNLIST_SKILLS_DIR || join(home, ".agents", "skills")); -const skills = readdirSync(sourceRoot, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name) - .sort(); - -function lstatOrNull(path) { +} else { try { - return lstatSync(path); + registerSkills({ + sourceRoot: resolve(packageRoot, "skills"), + scope: registrationScope(args), + dryRun: args.includes("--dry-run"), + }); } catch (error) { - if (error.code === "ENOENT") return null; - throw error; - } -} - -function linkedSource(path) { - return resolve(dirname(path), readlinkSync(path)); -} - -const registrations = skills.map((name) => { - if (!/^[a-z0-9][a-z0-9-]*$/u.test(name)) { - throw new Error(`unsafe skill folder name: ${name}`); - } - const source = resolve(sourceRoot, name); - if (!existsSync(join(source, "SKILL.md"))) { - throw new Error(`skill ${name} is missing SKILL.md`); - } - const target = resolve(targetRoot, name); - const stat = lstatOrNull(target); - if (!stat) return { action: "link", name, source, target }; - if (!stat.isSymbolicLink()) { - throw new Error(`${target} already exists and is not a Burnlist-managed symlink`); - } - if (linkedSource(target) !== source) { - throw new Error(`${target} already links to a different skill source`); + console.error(`Burnlist: ${error.message}`); + process.exitCode = 1; } - return { action: "keep", name, source, target }; -}); - -if (!dryRun) mkdirSync(targetRoot, { recursive: true }); - -for (const registration of registrations) { - const verb = registration.action === "keep" ? "kept" : dryRun ? "would link" : "linked"; - if (registration.action === "link" && !dryRun) { - symlinkSync( - registration.source, - registration.target, - process.platform === "win32" ? "junction" : "dir", - ); - } - console.log(`Burnlist: ${verb} ${registration.name} -> ${registration.target}`); } - -console.log(`Burnlist: agent skills are registered under ${targetRoot}.`); diff --git a/scripts/smoke-global-install.mjs b/scripts/smoke-global-install.mjs index c815052..f9885c9 100755 --- a/scripts/smoke-global-install.mjs +++ b/scripts/smoke-global-install.mjs @@ -18,8 +18,13 @@ const home = join(tmpRoot, "home"); const prefix = join(tmpRoot, "prefix"); const packRoot = join(tmpRoot, "pack"); const npmCache = join(tmpRoot, "npm-cache"); +const { + BURNLIST_CLAUDE_SKILLS_DIR: ignoredClaudeSkillsDir, + BURNLIST_SKILLS_DIR: ignoredCodexSkillsDir, + ...smokeEnv +} = process.env; const env = { - ...process.env, + ...smokeEnv, HOME: home, USERPROFILE: home, npm_config_cache: npmCache, @@ -41,8 +46,8 @@ function run(command, args, options = {}) { return options.capture ? result.stdout.trim() : ""; } -function assertManagedLink(name, packageRoot) { - const target = join(home, ".agents", "skills", name); +function assertManagedLink(agentDirectory, name, packageRoot) { + const target = join(home, agentDirectory, "skills", name); const stat = lstatSync(target); if (!stat.isSymbolicLink()) throw new Error(`${target} is not a symlink`); const actual = realpathSync(resolve(dirname(target), readlinkSync(target))); @@ -68,7 +73,8 @@ try { run("npm", ["install", "--global", "--prefix", prefix, tarball]); const globalRoot = run("npm", ["root", "--global", "--prefix", prefix], { capture: true }); const packageRoot = resolve(globalRoot, "burnlist"); - assertManagedLink("burnlist", packageRoot); + assertManagedLink(".claude", "burnlist", packageRoot); + assertManagedLink(".agents", "burnlist", packageRoot); const cli = process.platform === "win32" ? join(prefix, "burnlist.cmd") @@ -95,13 +101,15 @@ try { || JSON.stringify(Object.keys(sdk).sort()) !== JSON.stringify(expected.sort())) process.exit(1); `]); - run(cli, ["uninstall"]); - for (const name of ["burnlist"]) { - try { - lstatSync(join(home, ".agents", "skills", name)); - throw new Error(`uninstall left the ${name} skill registration behind`); - } catch (error) { - if (error.code !== "ENOENT") throw error; + run(cli, ["uninstall", "--global", "--purge"]); + for (const agentDirectory of [".claude", ".agents"]) { + for (const name of ["burnlist"]) { + try { + lstatSync(join(home, agentDirectory, "skills", name)); + throw new Error(`uninstall left the ${agentDirectory} ${name} skill registration behind`); + } catch (error) { + if (error.code !== "ENOENT") throw error; + } } } console.log("Global npm install smoke test passed."); diff --git a/scripts/unregister-skills.mjs b/scripts/unregister-skills.mjs index a0c71bc..421bc67 100755 --- a/scripts/unregister-skills.mjs +++ b/scripts/unregister-skills.mjs @@ -1,43 +1,19 @@ #!/usr/bin/env node -import { lstatSync, readlinkSync, readdirSync, rmSync } from "node:fs"; -import { dirname, join, resolve } from "node:path"; +import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const args = new Set(process.argv.slice(2)); -const dryRun = args.has("--dry-run"); -const globalInstall = process.env.npm_config_global === "true" || args.has("--force-global"); - -if (!globalInstall) process.exit(0); - -const home = process.env.HOME || process.env.USERPROFILE; -if (!home) process.exit(0); +import { registrationScope, unregisterSkills } from "../src/cli/skills-register.mjs"; -const sourceRoot = resolve(packageRoot, "skills"); -const targetRoot = resolve(process.env.BURNLIST_SKILLS_DIR || join(home, ".agents", "skills")); -const skills = readdirSync(sourceRoot, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name) - .sort(); - -function lstatOrNull(path) { - try { - return lstatSync(path); - } catch (error) { - if (error.code === "ENOENT") return null; - throw error; - } -} +const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const args = process.argv.slice(2); -for (const name of skills) { - const source = resolve(sourceRoot, name); - const target = resolve(targetRoot, name); - const stat = lstatOrNull(target); - if (!stat) continue; - if (!stat.isSymbolicLink() || resolve(dirname(target), readlinkSync(target)) !== source) { - console.warn(`Burnlist: left ${target} untouched because it is not managed by this package.`); - continue; - } - if (!dryRun) rmSync(target, { force: true }); - console.log(`Burnlist: ${dryRun ? "would unlink" : "unlinked"} ${name} from ${target}.`); +try { + unregisterSkills({ + sourceRoot: resolve(packageRoot, "skills"), + scope: registrationScope(args), + dryRun: args.includes("--dry-run"), + }); +} catch (error) { + console.error(`Burnlist: ${error.message}`); + process.exitCode = 1; } diff --git a/scripts/verify-package.mjs b/scripts/verify-package.mjs index 0251505..8054e2d 100755 --- a/scripts/verify-package.mjs +++ b/scripts/verify-package.mjs @@ -32,6 +32,8 @@ const required = [ "package.json", "scripts/register-skills.mjs", "scripts/unregister-skills.mjs", + "src/cli/skills-install-cli.mjs", + "src/cli/skills-register.mjs", "src/cli/oven-cli.mjs", "src/cli/registry-cli.mjs", "src/ovens/oven-contract.mjs", diff --git a/scripts/verify-test-files.mjs b/scripts/verify-test-files.mjs index 260048a..e46f6c0 100644 --- a/scripts/verify-test-files.mjs +++ b/scripts/verify-test-files.mjs @@ -33,6 +33,13 @@ export const verificationTestFiles = [ "src/cli/lifecycle-moves.test.mjs", "src/cli/registry-cli.test.mjs", "src/cli/git-ignore.test.mjs", + "src/cli/skills-register.test.mjs", + "src/cli/skills-exclude.test.mjs", + "src/cli/skills-install-transaction.test.mjs", + "src/cli/atomic-quarantine.test.mjs", + "src/cli/skills-install-cli.test.mjs", + "src/cli/skills-install-cli-purge.test.mjs", + "src/cli/commands-help.test.mjs", "src/cli/oven-cli.test.mjs", "src/cli/oven-cli-stdout.test.mjs", "src/cli/oven-storage.test.mjs", diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 7cbbade..caba978 100755 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -23,6 +23,20 @@ function run(command, args, options = {}) { } } +function runCapture(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: repoRoot, + encoding: "utf8", + shell: false, + ...options, + }); + if (result.status !== 0) { + process.stderr.write(result.stderr || result.stdout || ""); + process.exit(result.status || 1); + } + return result.stdout; +} + function walkFiles(root, predicate) { const files = []; for (const entry of readdirSync(root, { withFileTypes: true })) { @@ -75,6 +89,9 @@ const sourceScanExcludes = [ ".playwright-cli/", "notes/burnlists/", "output/", + "website/node_modules/", + "website/dist/", + "website/.astro/", ]; function shouldScanSourceFile(path) { @@ -368,7 +385,8 @@ assertSourceExcludes("ovens/differential-testing/renderer/differential-testing-r assertSourceExcludes("src/server/burnlist-dashboard-server.mjs", "exact comparator when used", "Fallback Run Burn still requests the superseded manual comparator workflow."); assertSourceExcludes("dashboard/src/components/BurnOvens/BurnOvens.tsx", "exact comparator when used", "React Run Burn still requests the superseded manual comparator workflow."); assertSourceIncludes("skills/burnlist/SKILL.md", "references/burnlist-creation.md", "The Burnlist skill does not route creation work."); -assertSourceIncludes("scripts/register-skills.mjs", 'join(home, ".agents", "skills")', "Global npm install does not use the agent skill directory."); +assertSourceIncludes("src/cli/skills-register.mjs", 'join(home, ".claude", "skills")', "Global npm install does not use the Claude skill directory."); +assertSourceIncludes("src/cli/skills-register.mjs", 'join(home, ".agents", "skills")', "Global npm install does not use the Codex skill directory."); assertSourceIncludes("bin/burnlist.mjs", "Usage:", "Burnlist CLI help is missing."); assertSourceIncludes("bin/burnlist.mjs", 'args[0] === "uninstall"', "Burnlist CLI does not own safe uninstall cleanup."); assertSourceExcludes("README.md", "**Target**", "README still advertises the removed Target Oven."); @@ -387,9 +405,25 @@ assertPublishablePackage(); run(process.execPath, ["--test", ...verificationTestFiles]); -run(process.execPath, ["scripts/register-skills.mjs", "--force-global", "--dry-run"], { - env: { ...process.env, HOME: resolve(repoRoot, "fixtures", "npm-home") }, +const { + BURNLIST_CLAUDE_SKILLS_DIR: ignoredClaudeSkillsDir, + BURNLIST_SKILLS_DIR: ignoredCodexSkillsDir, + ...verificationEnv +} = process.env; +const verificationHome = resolve(repoRoot, "fixtures", "npm-home"); +const skillDryRun = runCapture(process.execPath, ["scripts/register-skills.mjs", "--force-global", "--dry-run"], { + env: { ...verificationEnv, HOME: verificationHome }, }); +for (const target of [ + join(verificationHome, ".claude", "skills", "burnlist"), + join(verificationHome, ".agents", "skills", "burnlist"), +]) { + if (!skillDryRun.includes(target)) { + console.error(`Global skill registration dry-run did not include ${target}.`); + process.exit(1); + } +} +process.stdout.write(skillDryRun); run(process.execPath, ["bin/burnlist.mjs", "--version"]); run(process.execPath, ["bin/burnlist.mjs", "--stamp"]); run(process.execPath, ["bin/burnlist.mjs", "differential-testing", "schema"]); diff --git a/skills/burnlist/SKILL.md b/skills/burnlist/SKILL.md index 229e619..b03e6a5 100644 --- a/skills/burnlist/SKILL.md +++ b/skills/burnlist/SKILL.md @@ -23,6 +23,7 @@ Read references only when their trigger applies: - `references/burnlist-splitting-lanes.md`: split/reorder decisions, recursive gates, parent/lane Burnlists, parallel lane handoff. - `references/burnlist-visible-output.md`: detailed silence rules, forbidden narration examples, checkpoint policy. - `references/burnlist-dashboard.md`: dashboard/chart/log/timeline/repo-graph behavior or dashboard repair only. +- `references/installation.md`: installing or removing the agent skill or Streaming Diff edit-capture hooks. - `references/oven-authoring.md`: authoring or inspecting Ovens from the `burnlist oven` CLI, the widget/format vocabulary, and source-binding conventions. Do not load cold references for a normal single-item implementation unless needed. If a task touches a cold-rule area, read the matching reference before editing Burnlist state in that area. @@ -132,3 +133,12 @@ Ovens can also be authored and inspected from the CLI: `burnlist oven /.claude/skills/burnlist` for Claude Code and `/.agents/skills/burnlist` for Codex. `--global` instead uses `~/.claude/skills/burnlist` and `~/.agents/skills/burnlist`; a global npm installation of Burnlist automatically registers both global skills. Use `--commit` only for a per-repository portable copy intended for Git; `--agent codex,claude` limits targets and `--dry-run` previews. `burnlist uninstall` is the inverse; `burnlist uninstall --global --purge` also removes the global npm package. +- **Streaming Diff hooks** (`burnlist hooks install`) install per-repository edit-capture commands, not skills. Codex consumes `/.codex/hooks.json`; Claude Code consumes `/.claude/settings.json`. They invoke `burnlist streaming-diff hook` for session/edit events and merge with existing hook entries. Hooks have no global mode: use `burnlist hooks uninstall` or `burnlist hooks status` in the repository, optionally with `--agent codex,claude`. `--untracked` asks install to add the config to `.git/info/exclude`; it cannot hide an already tracked config. + +Install only the system the task needs, or both. Read `references/installation.md` for exact commands, ownership, and shared-versus-local behavior. diff --git a/skills/burnlist/references/installation.md b/skills/burnlist/references/installation.md new file mode 100644 index 0000000..33b0571 --- /dev/null +++ b/skills/burnlist/references/installation.md @@ -0,0 +1,84 @@ +# Agent Skill and Hook Installation + +Burnlist offers two independent integrations. The **skill** tells an agent how to create and execute Burnlists. The **hooks** capture editing activity for Streaming Diff. Installing one does not install, require, or remove the other. + +## Skill Discovery + +The skills CLI surface is: + +```sh +burnlist install [--global] [--agent codex,claude] [--dry-run] [--commit] [--force] +burnlist uninstall [--global] [--agent codex,claude] [--dry-run] [--purge] +``` + +By default, `burnlist install` registers the bundled Burnlist skill for both agents in the current repository: + +| Agent | Per-repository target | Global target (`--global`) | +| --- | --- | --- | +| Claude Code | `/.claude/skills/burnlist` | `~/.claude/skills/burnlist` | +| Codex | `/.agents/skills/burnlist` | `~/.agents/skills/burnlist` | + +The default per-repository mode is a managed symlink and adds its target to `.git/info/exclude`, so it stays local and untracked. `--global` creates the managed global registrations instead. A global npm installation of Burnlist automatically registers those global skills for both agents. `--commit` is per-repository only: it creates a portable managed copy and removes Burnlist's local exclusion entry so the copy can be added to Git. `--force` permits an untracked managed copy to be downgraded to a symlink; tracked copies must be removed through Git first. `--agent codex`, `--agent claude`, or `--agent codex,claude` limits registrations; without it, both agents are targeted. `--dry-run` prints the planned link or copy operations without writing. + +For a Git worktree, the command reports the default mode as `untracked (local, .git/info/exclude)`. For `--commit`, it checks copied content files and reports either `committable (portable copy; run git add to track)` or the actual ignore rule still hiding content. Global registrations report `global symlink (no repo exclude)`. A non-Git directory instead reports `symlink (no git repo to exclude into)` or `portable copy (no git repo)`. + +`burnlist uninstall` removes only Burnlist-managed registrations in the matching scope and removes its matching local exclusion entries. `--purge` requires `uninstall --global`, targets both agents, and also uninstalls the global npm package. + +## Streaming Diff Edit-Capture Hooks + +The hooks CLI surface is: + +```sh +burnlist hooks [install|uninstall|status] [--agent codex,claude] [--untracked] +``` + +Bare `burnlist hooks` defaults to `status`. + +`burnlist hooks install` is repository-only and must run inside a Git worktree; there is no `--global` flag. It adds managed `burnlist streaming-diff hook` commands while preserving unrelated hook entries: + +| Agent that consumes the hook | Config written at the worktree root | Events | +| --- | --- | --- | +| Codex | `/.codex/hooks.json` | `SessionStart`, `PreToolUse`, `PostToolUse` | +| Claude Code | `/.claude/settings.json` | `SessionStart`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure` | + +The edit events are matched to each agent's write/edit tools, so the configured commands capture Streaming Diff activity around edits. Codex hook support requires Codex CLI 0.124.0 or newer; `status` reports whether the installed CLI can run the configured hooks. The hook commands require `burnlist` to be available on the host `PATH`; each agent may still ask for its own hook trust or consent. + +By default, an untracked hook config is added to `.git/info/exclude`, making it local. A tracked config remains shared with the team. `--untracked` asks install to add the config to that local exclude file even when it is tracked, but Git cannot hide an already tracked file. Burnlist records only configs it created under `/.local/burnlist/` so uninstall can remove an otherwise-empty created config; it removes only its exact hook entries and leaves unrelated configuration intact. + +Use `burnlist hooks status` to report each selected agent's hook state, whether its config is tracked or local, and CLI capability. `burnlist hooks uninstall` removes Burnlist's managed hook entries and its matching local-exclude entry. Both default to Codex and Claude; use `--agent codex`, `--agent claude`, or `--agent codex,claude` to limit the operation. + +The status output uses hook states `installed`, `none`, `partial`, or `corrupt`; it labels configuration as `shared with the team; info/exclude cannot hide tracked config`, `local (listed in .git/info/exclude)`, or `local (not listed in .git/info/exclude)`, and shows the config path inspected. Capability output is labeled by CLI (`codex cli:` or `claude cli:`) and is `installed+hooks-supported`, `installed-but-hooks-unsupported` (including the required minimum), or `not-installed`. + +## Common Commands + +Run these from the repository for per-repository integrations: + +```sh +# Skill only +burnlist install + +# Hooks only +burnlist hooks install + +# Both systems +burnlist install && burnlist hooks install + +# Global skill only (hooks have no global mode) +burnlist install --global + +# Global skill plus this repository's hooks +burnlist install --global && burnlist hooks install + +# Remove the per-repository skill only +burnlist uninstall + +# Remove the hooks only +burnlist hooks uninstall + +# Remove both per-repository systems +burnlist uninstall && burnlist hooks uninstall + +# Remove global skill registrations; add --purge to also uninstall global npm Burnlist +burnlist uninstall --global +burnlist uninstall --global --purge +``` diff --git a/src/cli/atomic-quarantine.mjs b/src/cli/atomic-quarantine.mjs new file mode 100644 index 0000000..8a6dc25 --- /dev/null +++ b/src/cli/atomic-quarantine.mjs @@ -0,0 +1,89 @@ +import { lstatSync, mkdtempSync, renameSync, rmSync, rmdirSync } from "node:fs"; +import { basename, dirname, join } from "node:path"; + +function lstatOrNull(path) { + try { return lstatSync(path); } catch (error) { + if (error.code === "ENOENT") return null; + throw error; + } +} + +// {dev, ino} alone cannot prove "this is the exact object I created": Linux +// (ext4 and friends) reuses inode numbers as soon as the old one is unlinked, +// so a foreign object that replaces ours at the same path can land on the +// identical {dev, ino} by pure allocator coincidence — inode uniqueness is a +// macOS/APFS behavior, not a POSIX guarantee. Pairing the pair with mtimeMs +// (set fresh at creation, never inherited from a prior tenant of a reused +// inode) means a false match additionally requires the replacement to land +// in the very same clock tick as the original. mtime is deliberately used +// instead of ctime: quarantineTarget below holds an object by *renaming* it +// to a private path before validating it, and rename(2) always bumps ctime +// (even for the exact same object) but never touches mtime — so ctime would +// make every legitimate match look foreign, while mtime survives it. For a +// local, single-process, lock-serialized installer this tuple is a +// proportionate, best-effort defense-in-depth check — not a cryptographic +// guarantee — and it is honest cross-platform where a bare {dev, ino} check +// was not. +export function filesystemIdentity(path) { + const stat = lstatSync(path); + return { dev: stat.dev, ino: stat.ino, mtimeMs: stat.mtimeMs }; +} + +export function sameFilesystemIdentity(stat, identity) { + return Boolean( + stat && identity && stat.dev === identity.dev && stat.ino === identity.ino && stat.mtimeMs === identity.mtimeMs, + ); +} + +// Rename to an owned quarantine path before deciding whether the object is +// ours. The returned object must be removed or restored by the caller. +export function quarantineTarget({ target, quarantined, identity, validate = () => true, hooks }) { + hooks?.beforeQuarantine?.({ target, quarantined }); + let held = false; + try { + try { renameSync(target, quarantined); } catch (error) { + if (error.code === "ENOENT") return { status: "missing" }; + throw error; + } + held = true; + const stat = lstatOrNull(quarantined); + if ((identity && !sameFilesystemIdentity(stat, identity)) || !validate(quarantined, stat)) { + renameSync(quarantined, target); + held = false; + return { status: "foreign" }; + } + return { status: "quarantined", quarantined }; + } catch (error) { + if (held && lstatOrNull(quarantined)) { + try { renameSync(quarantined, target); } catch (restoreError) { + throw new AggregateError([error, restoreError], `could not remove quarantined target ${target}`); + } + } + throw error; + } +} + +// Deletion is the common quarantine operation. It always removes the private +// quarantined pathname, never the caller-visible target pathname. +export function removeQuarantinedTarget({ target, identity, validate = () => true, remove = rmSync, hooks }) { + const parent = dirname(target); + const container = mkdtempSync(join(parent, `.${basename(target)}.burnlist-quarantine-`)); + const quarantined = join(container, "object"); + try { + const held = quarantineTarget({ target, quarantined, identity, validate, hooks }); + if (held.status !== "quarantined") return held; + try { remove(quarantined, { recursive: true, force: true }); } catch (error) { + if (lstatOrNull(quarantined)) { + try { renameSync(quarantined, target); } catch (restoreError) { + throw new AggregateError([error, restoreError], `could not remove quarantined target ${target}`); + } + } + throw error; + } + return { status: "removed" }; + } finally { + try { rmdirSync(container); } catch (error) { + if (error.code !== "ENOENT" && error.code !== "ENOTEMPTY") throw error; + } + } +} diff --git a/src/cli/atomic-quarantine.test.mjs b/src/cli/atomic-quarantine.test.mjs new file mode 100644 index 0000000..e46f5ba --- /dev/null +++ b/src/cli/atomic-quarantine.test.mjs @@ -0,0 +1,41 @@ +import assert from "node:assert/strict"; +import { lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; + +import { filesystemIdentity, removeQuarantinedTarget, sameFilesystemIdentity } from "./atomic-quarantine.mjs"; + +test("filesystem identity rejects a reused-inode match on mtimeMs alone", () => { + // Linux reuses inode numbers as soon as a path is unlinked, so a foreign + // replacement can land on the exact same {dev, ino} as the object it + // replaced. Prove the guard does not treat that coincidence as a match by + // constructing the pathological case directly, without depending on any + // real filesystem's inode allocator behavior. + const original = { dev: 1, ino: 42, mtimeMs: 1000 }; + const reusedInodeReplacement = { dev: 1, ino: 42, mtimeMs: 2000 }; + assert.equal(sameFilesystemIdentity(reusedInodeReplacement, original), false); + assert.equal(sameFilesystemIdentity(original, original), true); +}); + +test("atomic quarantine restores a foreign target interleaved before ownership validation", () => { + const root = mkdtempSync(join(tmpdir(), "burnlist-atomic-quarantine-")); + try { + const target = join(root, "skill"); + mkdirSync(target); + const identity = filesystemIdentity(target); + const result = removeQuarantinedTarget({ + target, + identity, + validate: (path) => lstatSync(path).isDirectory(), + hooks: { + beforeQuarantine: () => { + rmSync(target, { recursive: true, force: true }); + writeFileSync(target, "foreign\n"); + }, + }, + }); + assert.equal(result.status, "foreign"); + assert.equal(readFileSync(target, "utf8"), "foreign\n"); + } finally { rmSync(root, { recursive: true, force: true }); } +}); diff --git a/src/cli/commands-help.test.mjs b/src/cli/commands-help.test.mjs new file mode 100644 index 0000000..ace46f9 --- /dev/null +++ b/src/cli/commands-help.test.mjs @@ -0,0 +1,82 @@ +import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; + +const root = resolve(fileURLToPath(new URL("../..", import.meta.url))); +const cli = join(root, "bin", "burnlist.mjs"); + +function fixture({ git = true } = {}) { + const directory = mkdtempSync(join(tmpdir(), "burnlist-command-help-")); + if (git) execFileSync("git", ["init", "--quiet", directory]); + return { directory, cleanup: () => rmSync(directory, { recursive: true, force: true }) }; +} + +function run(cwd, args) { + return spawnSync(process.execPath, [cli, ...args], { cwd, encoding: "utf8" }); +} + +test("install, uninstall, and hooks subcommand help exit successfully with usage", () => { + const context = fixture({ git: false }); + try { + for (const [args, usage] of [ + [["install", "--help"], /Usage: burnlist install/u], + [["uninstall", "--help"], /Usage: burnlist uninstall/u], + [["hooks", "install", "--help"], /Usage: burnlist hooks/u], + [["hooks", "uninstall", "--help"], /Usage: burnlist hooks/u], + [["hooks", "status", "--help"], /Usage: burnlist hooks/u], + ]) { + const result = run(context.directory, args); + assert.equal(result.status, 0, args.join(" ")); + assert.match(result.stdout, usage); + assert.doesNotMatch(result.stderr, /unexpected argument/u); + } + } finally { context.cleanup(); } +}); + +test("empty skill and hook uninstalls report that there is nothing to remove", () => { + const context = fixture(); + try { + for (const args of [["uninstall"], ["hooks", "uninstall"]]) { + const result = run(context.directory, args); + assert.equal(result.status, 0, args.join(" ")); + assert.match(result.stdout, /Burnlist: nothing installed to remove\./u); + } + } finally { context.cleanup(); } +}); + +test("hooks status labels CLI capability and identifies the inspected config", () => { + const context = fixture(); + try { + const result = run(context.directory, ["hooks", "status", "--agent", "codex"]); + assert.equal(result.status, 0); + assert.match(result.stdout, /codex: none;.*config .*\.codex\/hooks\.json/u); + assert.match(result.stdout, /^codex cli: /mu); + } finally { context.cleanup(); } +}); + +test("hooks install outside Git gives a friendly actionable error", () => { + const context = fixture({ git: false }); + try { + mkdirSync(join(context.directory, "nested")); + const result = run(join(context.directory, "nested"), ["hooks", "install"]); + assert.equal(result.status, 1); + assert.match(result.stderr, /hooks install must run inside a Git repository/u); + assert.doesNotMatch(result.stderr, /fatal:/u); + } finally { context.cleanup(); } +}); + +test("hooks status and uninstall name their own Git requirement", () => { + const context = fixture({ git: false }); + try { + for (const command of ["status", "uninstall"]) { + const result = run(context.directory, ["hooks", command]); + assert.equal(result.status, 1, command); + assert.match(result.stderr, new RegExp(`hooks ${command} must run inside a Git repository`, "u")); + assert.doesNotMatch(result.stderr, /hooks install must run/u); + } + } finally { context.cleanup(); } +}); diff --git a/src/cli/hooks-cli.mjs b/src/cli/hooks-cli.mjs index fa052eb..43dcb22 100644 --- a/src/cli/hooks-cli.mjs +++ b/src/cli/hooks-cli.mjs @@ -25,19 +25,23 @@ function parse() { function print(result, { install = false } = {}) { for (const entry of result) { const shared = entry.mode === "tracked" ? "shared with the team; info/exclude cannot hide tracked config" : entry.excluded ? "local (listed in .git/info/exclude)" : "local (not listed in .git/info/exclude)"; - console.log(`${entry.agent}: ${entry.state ?? (entry.installed ? "installed" : "none")}; ${shared}`); + console.log(`${entry.agent}: ${entry.state ?? (entry.installed ? "installed" : "none")}; ${shared}; config ${entry.path}`); const capability = entry.capability; - console.log(`${entry.agent}: ${capability.state}${capability.minimumVersion ? ` (needs >= ${capability.minimumVersion})` : ""}`); + console.log(`${entry.agent} cli: ${capability.state}${capability.minimumVersion ? ` (needs >= ${capability.minimumVersion})` : ""}`); if (install && capability.state === "installed-but-hooks-unsupported") console.warn(`${entry.agent}: hooks were configured but this installed CLI cannot run them.`); if (entry.forcedUntracked) console.warn(`${entry.agent}: --untracked cannot hide an already tracked config.`); } } try { - if (["--help", "-h"].includes(subcommand)) console.log("Usage: burnlist hooks [--agent codex,claude] [--untracked]"); + if (["--help", "-h"].includes(subcommand) || tokens.includes("--help") || tokens.includes("-h")) console.log("Usage: burnlist hooks [--agent codex,claude] [--untracked]"); else { const options = parse(); if (subcommand === "install") print(updateHookConfigs({ ...options, install: true }), { install: true }); - else if (subcommand === "uninstall") print(updateHookConfigs({ ...options, install: false })); + else if (subcommand === "uninstall") { + const result = updateHookConfigs({ ...options, install: false }); + print(result); + if (result.every((entry) => !entry.removed)) console.log("Burnlist: nothing installed to remove."); + } else if (subcommand === "status") print(hookConfigStatus(options)); else fail(`unknown subcommand \"${subcommand}\"`); } diff --git a/src/cli/hooks-config.mjs b/src/cli/hooks-config.mjs index e3678a5..7bd9f49 100644 --- a/src/cli/hooks-config.mjs +++ b/src/cli/hooks-config.mjs @@ -1,9 +1,9 @@ -import { randomBytes } from "node:crypto"; import { spawnSync } from "node:child_process"; -import { closeSync, constants, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"; -import { basename, dirname, join, relative, resolve } from "node:path"; +import { existsSync, readFileSync, rmSync } from "node:fs"; +import { dirname, join, relative, resolve } from "node:path"; import { gitProbe } from "./git-ignore.mjs"; +import { addOwnedLocalExcludeText, fsyncDirectory, gitExcludePath, localExcludeTarget, removeOwnedLocalExcludeText, writeAtomicText } from "./local-exclude.mjs"; import { containedJoin, withRepoStateLock } from "../server/repo-state.mjs"; export const HOOK_MARKER = "burnlist-managed:streaming-diff-hooks@1"; @@ -22,23 +22,7 @@ const MUTATING_MATCHERS = { codex: "apply_patch|write_file|edit_file|create_file|delete_file|rename_file|move_file", }; -function fsyncDirectory(path) { - const fd = openSync(path, constants.O_RDONLY); - try { fsyncSync(fd); } finally { closeSync(fd); } -} - -function writeDurableText(path, text) { - mkdirSync(dirname(path), { recursive: true }); - const temporary = join(dirname(path), `.${basename(path)}.${randomBytes(8).toString("hex")}.tmp`); - let fd; - try { - fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600); - writeFileSync(fd, text); fsyncSync(fd); closeSync(fd); fd = undefined; - renameSync(temporary, path); fsyncDirectory(dirname(path)); - } finally { if (fd !== undefined) closeSync(fd); rmSync(temporary, { force: true }); } -} - -export function writeDurableJson(path, value) { writeDurableText(path, `${JSON.stringify(value, null, 2)}\n`); } +export function writeDurableJson(path, value) { writeAtomicText(path, `${JSON.stringify(value, null, 2)}\n`); } function provenancePath(repoRoot) { return containedJoin(repoRoot, "hooks-config-provenance.json"); } function configKey(repoRoot, path) { return relative(resolve(repoRoot), path).replace(/\\/gu, "/"); } @@ -143,17 +127,20 @@ function configPath(repoRoot, agent) { return join(resolve(repoRoot), spec.file); } -function worktreeRoot(repoRoot) { +function worktreeRoot(repoRoot, operation) { const cwd = resolve(repoRoot); const gitRoot = gitProbe(cwd, ["rev-parse", "--show-toplevel"]); + if (gitRoot.status === 128 && /not a git repository/iu.test(gitRoot.stderr ?? "")) { + throw new Error(`hooks ${operation} must run inside a Git repository.`); + } if (gitRoot.status !== 0) throw new Error(gitRoot.error?.message || gitRoot.stderr?.trim() || "could not determine Git worktree root"); const root = gitRoot.stdout.trim(); if (!root) throw new Error("could not determine Git worktree root"); return resolve(cwd, root); } -function preflight(repoRoot, agents) { - const root = worktreeRoot(repoRoot); +function preflight(repoRoot, agents, operation) { + const root = worktreeRoot(repoRoot, operation); const targets = agents.map((agent) => { const path = configPath(root, agent); const target = relative(root, path).replace(/\\/gu, "/"); @@ -164,33 +151,9 @@ function preflight(repoRoot, agents) { return { root, targets }; } -function excludePath(repoRoot) { - const result = gitProbe(repoRoot, ["rev-parse", "--git-path", "info/exclude"]); - if (result.status !== 0) throw new Error(result.error?.message || result.stderr?.trim() || "could not locate .git/info/exclude"); - return resolve(repoRoot, result.stdout.trim()); -} - -function excludeTarget(repoRoot, path) { return `/${relative(resolve(repoRoot), path).replace(/\\/gu, "/")}`; } - -function addLocalExcludeText(content, target) { - if (content.split(/\r?\n/u).includes(target)) return; - const prefix = content && !content.endsWith("\n") ? `${content}\n` : content; - return `${prefix}# ${HOOK_MARKER}\n${target}\n`; -} - -function removeLocalExcludeText(content, target) { - const lines = content.split(/\r?\n/u); - const kept = []; - for (let index = 0; index < lines.length; index += 1) { - if (lines[index] === `# ${HOOK_MARKER}` && lines[index + 1] === target) { index += 1; continue; } - kept.push(lines[index]); - } - return kept.join("\n"); -} - function locallyExcluded(repoRoot, path) { - const exclude = excludePath(repoRoot); - return existsSync(exclude) && readFileSync(exclude, "utf8").split(/\r?\n/u).includes(excludeTarget(repoRoot, path)); + const exclude = gitExcludePath(repoRoot); + return existsSync(exclude) && readFileSync(exclude, "utf8").split(/\r?\n/u).includes(localExcludeTarget(repoRoot, path)); } function excludedIn(content, target) { return content.split(/\r?\n/u).includes(target); } @@ -200,14 +163,14 @@ function applyFileChange(change, writeJson) { rmSync(change.path, { force: true }); fsyncDirectory(dirname(change.path)); } else if (change.value) writeJson(change.path, change.value); - else writeDurableText(change.path, change.after); + else writeAtomicText(change.path, change.after); } function restoreFileChange(change) { if (change.before === undefined) { rmSync(change.path, { force: true }); fsyncDirectory(dirname(change.path)); - } else writeDurableText(change.path, change.before); + } else writeAtomicText(change.path, change.before); } function versionAtLeast(version, minimum) { @@ -235,7 +198,7 @@ export function hookCapability(agent, { spawn = spawnSync, env = process.env } = } export function hookConfigStatus({ repoRoot = process.cwd(), agents = Object.keys(AGENTS), capability = hookCapability } = {}) { - const { root, targets } = preflight(repoRoot, agents); + const { root, targets } = preflight(repoRoot, agents, "status"); return targets.map(({ agent, path, tracked }) => { let installed = false; let malformed = false; @@ -256,7 +219,7 @@ export function hookConfigStatus({ repoRoot = process.cwd(), agents = Object.key export function updateHookConfigs({ repoRoot = process.cwd(), agents = Object.keys(AGENTS), install, untracked = false, capability = hookCapability, writeJson = writeDurableJson, restoreFile = restoreFileChange } = {}) { if (typeof install !== "boolean") throw new Error("install must be true or false"); - const { root, targets } = preflight(repoRoot, agents); + const { root, targets } = preflight(repoRoot, agents, install ? "install" : "uninstall"); return withRepoStateLock(root, () => { const capabilities = new Map(targets.map(({ agent }) => [agent, capability(agent)])); const created = readProvenance(root); @@ -274,16 +237,17 @@ export function updateHookConfigs({ repoRoot = process.cwd(), agents = Object.ke if (!install && created.delete(key)) provenanceChanged = true; return { agent, path, tracked, key, next, changed, remove, + removed: !install && ownershipState(config, agent) !== "none", change: changed || remove ? { path, before: state.text, after: remove ? undefined : "", value: remove ? undefined : next } : null, }; }); - const exclude = excludePath(root); + const exclude = gitExcludePath(root); const excludeBefore = readOptionalText(exclude); let excludeAfter = excludeBefore ?? ""; for (const { path, tracked } of prepared) { - const target = excludeTarget(root, path); - if (install && (!tracked || untracked)) excludeAfter = addLocalExcludeText(excludeAfter, target) ?? excludeAfter; - if (!install) excludeAfter = removeLocalExcludeText(excludeAfter, target); + const target = localExcludeTarget(root, path); + if (install && (!tracked || untracked)) excludeAfter = addOwnedLocalExcludeText(excludeAfter, target, HOOK_MARKER) ?? excludeAfter; + if (!install) excludeAfter = removeOwnedLocalExcludeText(excludeAfter, target, HOOK_MARKER); } if (excludeBefore === undefined && excludeAfter === "") excludeAfter = undefined; const changes = prepared.flatMap(({ change }) => change ? [change] : []); @@ -308,12 +272,13 @@ export function updateHookConfigs({ repoRoot = process.cwd(), agents = Object.ke } throw error; } - return prepared.map(({ agent, path, tracked, next }) => { + return prepared.map(({ agent, path, tracked, next, removed }) => { const resulting = install ? next : Object.keys(next).length === 0 ? {} : next; return { agent, path, installed: hasOwnedEntries(resulting, agent), state: ownershipState(resulting, agent), - mode: tracked ? "tracked" : "untracked", excluded: excludedIn(excludeAfter ?? "", excludeTarget(root, path)), + mode: tracked ? "tracked" : "untracked", excluded: excludedIn(excludeAfter ?? "", localExcludeTarget(root, path)), forcedUntracked: untracked && tracked, capability: capabilities.get(agent), + removed, }; }); }); diff --git a/src/cli/local-exclude.mjs b/src/cli/local-exclude.mjs new file mode 100644 index 0000000..a63157d --- /dev/null +++ b/src/cli/local-exclude.mjs @@ -0,0 +1,62 @@ +import { randomBytes } from "node:crypto"; +import { closeSync, constants, fsyncSync, mkdirSync, openSync, renameSync, rmSync, writeFileSync } from "node:fs"; +import { basename, dirname, join, relative, resolve } from "node:path"; + +import { gitProbe } from "./git-ignore.mjs"; + +export function fsyncDirectory(path) { + const fd = openSync(path, constants.O_RDONLY); + try { fsyncSync(fd); } finally { closeSync(fd); } +} + +// A staged durable writer lets guarded callers validate immediately before swap. +export function stageAtomicText(path, text) { + mkdirSync(dirname(path), { recursive: true }); + const temporary = join(dirname(path), `.${basename(path)}.${randomBytes(8).toString("hex")}.tmp`); + let fd; + let staged = false; + try { + fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600); + writeFileSync(fd, text); fsyncSync(fd); closeSync(fd); fd = undefined; + staged = true; + return { + commit() { renameSync(temporary, path); fsyncDirectory(dirname(path)); }, + discard() { rmSync(temporary, { force: true }); }, + }; + } finally { + if (fd !== undefined) closeSync(fd); + if (!staged) rmSync(temporary, { force: true }); + } +} + +// A single durable writer is shared by all CLI users of .git/info/exclude. +export function writeAtomicText(path, text) { + const staged = stageAtomicText(path, text); + try { staged.commit(); } finally { staged.discard(); } +} + +export function gitExcludePath(repoRoot) { + const result = gitProbe(repoRoot, ["rev-parse", "--git-path", "info/exclude"]); + if (result.status !== 0) throw new Error(result.error?.message || result.stderr?.trim() || "could not locate .git/info/exclude"); + return resolve(repoRoot, result.stdout.trim()); +} + +export function localExcludeTarget(repoRoot, path) { + return `/${relative(resolve(repoRoot), path).replace(/\\/gu, "/")}`; +} + +export function addOwnedLocalExcludeText(content, target, marker) { + if (content.split(/\r?\n/u).includes(target)) return; + const prefix = content && !content.endsWith("\n") ? `${content}\n` : content; + return `${prefix}# ${marker}\n${target}\n`; +} + +export function removeOwnedLocalExcludeText(content, target, marker) { + const lines = content.split(/\r?\n/u); + const kept = []; + for (let index = 0; index < lines.length; index += 1) { + if (lines[index] === `# ${marker}` && lines[index + 1] === target) { index += 1; continue; } + kept.push(lines[index]); + } + return kept.join("\n"); +} diff --git a/src/cli/skills-exclude.mjs b/src/cli/skills-exclude.mjs new file mode 100644 index 0000000..a8dcb00 --- /dev/null +++ b/src/cli/skills-exclude.mjs @@ -0,0 +1,79 @@ +import { randomBytes } from "node:crypto"; +import { chmodSync, lstatSync, mkdirSync, readFileSync, readlinkSync, renameSync, rmSync, symlinkSync, utimesSync } from "node:fs"; +import { dirname } from "node:path"; + +import { filesystemIdentity, removeQuarantinedTarget, sameFilesystemIdentity } from "./atomic-quarantine.mjs"; +import { stageAtomicText, writeAtomicText } from "./local-exclude.mjs"; + +function lstatOrNull(path) { + try { return lstatSync(path); } catch (error) { + if (error.code === "ENOENT") return null; + throw error; + } +} + +export function snapshotExclude(path) { + const stat = lstatOrNull(path); + if (!stat) return { kind: "missing" }; + if (stat.isSymbolicLink()) { + return { kind: "symlink", identity: filesystemIdentity(path), link: readlinkSync(path), text: readFileSync(path, "utf8"), mode: stat.mode, atime: stat.atime, mtime: stat.mtime }; + } + if (!stat.isFile()) throw new Error(`refusing to modify non-file git exclude path: ${path}`); + return { kind: "file", identity: filesystemIdentity(path), text: readFileSync(path, "utf8"), mode: stat.mode, atime: stat.atime, mtime: stat.mtime }; +} + +export function matchesExcludeSnapshot(path, snapshot) { + if (snapshot.kind === "missing") return !lstatOrNull(path); + const stat = lstatOrNull(path); + if (!sameFilesystemIdentity(stat, snapshot.identity)) return false; + if (snapshot.kind === "symlink") return stat.isSymbolicLink() && readlinkSync(path) === snapshot.link && readFileSync(path, "utf8") === snapshot.text; + return stat.isFile() && !stat.isSymbolicLink() && readFileSync(path, "utf8") === snapshot.text; +} + +function restoreObject(path, snapshot) { + mkdirSync(dirname(path), { recursive: true }); + if (snapshot.kind === "symlink") { + const temporary = `${path}.burnlist-restore-${randomBytes(12).toString("hex")}`; + try { + symlinkSync(snapshot.link, temporary); + renameSync(temporary, path); + } finally { rmSync(temporary, { force: true }); } + return; + } + writeAtomicText(path, snapshot.text); + chmodSync(path, snapshot.mode & 0o777); + utimesSync(path, snapshot.atime, snapshot.mtime); +} + +// The exclude file participates in target rollback. Its current object must +// still be the text/object this transaction wrote before we replace or remove +// it, so an outside edit is left alone instead of being overwritten. +export function restoreExcludeSnapshot({ path, before, written, remove = rmSync }) { + if (!written) { + if (!matchesExcludeSnapshot(path, before)) throw new Error(`could not restore exclude file because it changed during this transaction: ${path}`); + return; + } + if (!written || !matchesExcludeSnapshot(path, written)) { + throw new Error(`could not restore exclude file because it changed after this transaction wrote it: ${path}`); + } + const result = removeQuarantinedTarget({ + target: path, + identity: written.identity, + validate: (quarantined) => matchesExcludeSnapshot(quarantined, written), + remove, + }); + if (result.status !== "removed") throw new Error(`could not restore exclude file because it is no longer the object written by this transaction: ${path}`); + if (before.kind !== "missing") restoreObject(path, before); +} + +export function writeGuardedExclude({ path, before, text, stageAtomic = stageAtomicText, beforeSwap }) { + const staged = stageAtomic(path, text); + try { + beforeSwap?.(); + if (!matchesExcludeSnapshot(path, before)) { + throw new Error(`refusing to overwrite git exclude file because it changed before this transaction wrote it: ${path}`); + } + staged.commit(); + return snapshotExclude(path); + } finally { staged.discard(); } +} diff --git a/src/cli/skills-exclude.test.mjs b/src/cli/skills-exclude.test.mjs new file mode 100644 index 0000000..7bd5c71 --- /dev/null +++ b/src/cli/skills-exclude.test.mjs @@ -0,0 +1,23 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; + +import { snapshotExclude, writeGuardedExclude } from "./skills-exclude.mjs"; + +test("guarded exclude write preserves an edit made after staging", () => { + const root = mkdtempSync(join(tmpdir(), "burnlist-skills-exclude-")); + const path = join(root, "exclude"); + try { + writeFileSync(path, "# before\n"); + const before = snapshotExclude(path); + assert.throws(() => writeGuardedExclude({ + path, + before, + text: "# managed\n", + beforeSwap: () => writeFileSync(path, "# concurrent edit\n"), + }), /refusing to overwrite git exclude file because it changed/u); + assert.equal(readFileSync(path, "utf8"), "# concurrent edit\n"); + } finally { rmSync(root, { recursive: true, force: true }); } +}); diff --git a/src/cli/skills-install-cli-purge.test.mjs b/src/cli/skills-install-cli-purge.test.mjs new file mode 100644 index 0000000..ca17c8b --- /dev/null +++ b/src/cli/skills-install-cli-purge.test.mjs @@ -0,0 +1,204 @@ +import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; + +import { runSkillsInstallCli } from "./skills-install-cli.mjs"; + +const sourceRoot = resolve(fileURLToPath(new URL("../..", import.meta.url))); +const cli = join(sourceRoot, "bin", "burnlist.mjs"); +const skillSource = join(sourceRoot, "skills", "burnlist"); +const { BURNLIST_CLAUDE_SKILLS_DIR, BURNLIST_SKILLS_DIR, ...baseEnv } = process.env; + +function fixture() { + const root = mkdtempSync(join(tmpdir(), "burnlist-skills-install-cli-purge-")); + const repo = join(root, "repo"); + const home = join(root, "home"); + mkdirSync(repo); + mkdirSync(home); + execFileSync("git", ["init", "--quiet"], { cwd: repo }); + return { root, repo, home, cleanup: () => rmSync(root, { recursive: true, force: true }) }; +} + +function run(context, args, env = {}) { + return spawnSync(process.execPath, [cli, ...args], { + cwd: context.repo, + encoding: "utf8", + env: { ...baseEnv, HOME: context.home, USERPROFILE: context.home, ...env }, + }); +} + +function target(context, agent) { + return join(context.repo, agent === "claude" ? ".claude" : ".agents", "skills", "burnlist"); +} + +function assertLink(path, source = skillSource) { + assert.equal(lstatSync(path).isSymbolicLink(), true); + assert.equal(resolve(dirname(path), readlinkSync(path)), source); +} + +function exclude(context) { return readFileSync(join(context.repo, ".git", "info", "exclude"), "utf8"); } + +test("failed global purge runs npm first and leaves skill links untouched", () => { + const context = fixture(); + try { + const packageRoot = join(context.root, "npm", "lib", "node_modules", "burnlist"); + cpSync(join(sourceRoot, "skills"), join(packageRoot, "skills"), { recursive: true }); + const claudeSkills = join(context.root, "claude-skills"); + const codexSkills = join(context.root, "codex-skills"); + const env = { ...baseEnv, HOME: context.home, USERPROFILE: context.home, BURNLIST_CLAUDE_SKILLS_DIR: claudeSkills, BURNLIST_SKILLS_DIR: codexSkills }; + const logs = []; + const errors = []; + const packageSkill = join(packageRoot, "skills", "burnlist"); + assert.equal(runSkillsInstallCli({ args: ["install", "--global"], packageRoot, cwd: context.repo, env, log: (line) => logs.push(line) }), 0); + assertLink(join(claudeSkills, "burnlist"), packageSkill); + assertLink(join(codexSkills, "burnlist"), packageSkill); + const calls = []; + const failNpm = (command, args) => { + calls.push({ command, args }); + assertLink(join(claudeSkills, "burnlist"), packageSkill); + assertLink(join(codexSkills, "burnlist"), packageSkill); + return { status: 1 }; + }; + assert.equal(runSkillsInstallCli({ args: ["uninstall", "--global", "--purge"], packageRoot, cwd: context.repo, env, log: (line) => logs.push(line), error: (line) => errors.push(line), spawn: failNpm }), 1); + assert.deepEqual(calls, [{ command: process.platform === "win32" ? "npm.cmd" : "npm", args: ["uninstall", "--global", "--prefix", join(context.root, "npm"), "burnlist"] }]); + assertLink(join(claudeSkills, "burnlist"), packageSkill); + assertLink(join(codexSkills, "burnlist"), packageSkill); + assert.doesNotMatch(logs.join("\n"), /restored|removed/u); + assert.match(errors.join("\n"), /npm uninstall failed; checking global skill registrations for newly broken links/u); + } finally { context.cleanup(); } +}); + +test("global purge dry-run includes managed skill registrations", () => { + const context = fixture(); + try { + const env = { ...baseEnv, HOME: context.home, USERPROFILE: context.home }; + assert.equal(runSkillsInstallCli({ args: ["install", "--global"], packageRoot: sourceRoot, cwd: context.repo, env, log: () => {} }), 0); + const logs = []; + assert.equal(runSkillsInstallCli({ args: ["uninstall", "--global", "--purge", "--dry-run"], packageRoot: sourceRoot, cwd: context.repo, env, log: (line) => logs.push(line) }), 0); + assert.match(logs.join("\n"), /would uninstall the global npm package/u); + assert.match(logs.join("\n"), /would remove .*global managed registration/u); + } finally { context.cleanup(); } +}); + +test("npm-successful global purge aggregates every cleanup failure", () => { + const context = fixture(); + try { + const packageRoot = join(context.root, "npm", "lib", "node_modules", "burnlist"); + cpSync(join(sourceRoot, "skills"), join(packageRoot, "skills"), { recursive: true }); + const claudeSkills = join(context.root, "claude-skills"); + const codexSkills = join(context.root, "codex-skills"); + const env = { ...baseEnv, HOME: context.home, USERPROFILE: context.home, BURNLIST_CLAUDE_SKILLS_DIR: claudeSkills, BURNLIST_SKILLS_DIR: codexSkills }; + const packageSkill = join(packageRoot, "skills", "burnlist"); + assert.equal(runSkillsInstallCli({ args: ["install", "--global"], packageRoot, cwd: context.repo, env }), 0); + const attempted = []; + const errors = []; + const status = runSkillsInstallCli({ + args: ["uninstall", "--global", "--purge"], packageRoot, cwd: context.repo, env, + spawn: () => { + assertLink(join(claudeSkills, "burnlist"), packageSkill); + assertLink(join(codexSkills, "burnlist"), packageSkill); + return { status: 0 }; + }, + remove(path) { attempted.push(path); throw new Error(`blocked ${path}`); }, + error: (line) => errors.push(line), + }); + assert.equal(status, 1); + assert.equal(attempted.length, 2); + assert.ok(attempted.every((path) => /\.burnlist\.burnlist-quarantine-.+\/object$/u.test(path))); + assert.match(errors.join("\n"), /could not remove 2 global skill registration/u); + assertLink(join(claudeSkills, "burnlist"), packageSkill); + assertLink(join(codexSkills, "burnlist"), packageSkill); + } finally { context.cleanup(); } +}); + +test("failed npm purge removes and reports only newly dangling global links", () => { + const context = fixture(); + try { + const packageRoot = join(context.root, "npm", "lib", "node_modules", "burnlist"); + cpSync(join(sourceRoot, "skills"), join(packageRoot, "skills"), { recursive: true }); + const claudeSkills = join(context.root, "claude-skills"); + const codexSkills = join(context.root, "codex-skills"); + const env = { ...baseEnv, HOME: context.home, USERPROFILE: context.home, BURNLIST_CLAUDE_SKILLS_DIR: claudeSkills, BURNLIST_SKILLS_DIR: codexSkills }; + assert.equal(runSkillsInstallCli({ args: ["install", "--global"], packageRoot, cwd: context.repo, env }), 0); + const errors = []; + const status = runSkillsInstallCli({ + args: ["uninstall", "--global", "--purge"], packageRoot, cwd: context.repo, env, + spawn: () => { rmSync(packageRoot, { recursive: true, force: true }); return { status: 1 }; }, + error: (line) => errors.push(line), + }); + assert.equal(status, 1); + assert.equal(existsSync(join(claudeSkills, "burnlist")), false); + assert.equal(existsSync(join(codexSkills, "burnlist")), false); + assert.match(errors.join("\n"), /removed now-broken global skill link\(s\):/u); + assert.match(errors.join("\n"), new RegExp(claudeSkills, "u")); + assert.match(errors.join("\n"), new RegExp(codexSkills, "u")); + } finally { context.cleanup(); } +}); + +test("global purge rechecks snapshot ownership before removing registrations", () => { + const context = fixture(); + try { + const packageRoot = join(context.root, "npm", "lib", "node_modules", "burnlist"); + cpSync(join(sourceRoot, "skills"), join(packageRoot, "skills"), { recursive: true }); + const claudeSkills = join(context.root, "claude-skills"); + const codexSkills = join(context.root, "codex-skills"); + const env = { ...baseEnv, HOME: context.home, USERPROFILE: context.home, BURNLIST_CLAUDE_SKILLS_DIR: claudeSkills, BURNLIST_SKILLS_DIR: codexSkills }; + const packageSkill = join(packageRoot, "skills", "burnlist"); + assert.equal(runSkillsInstallCli({ args: ["install", "--global"], packageRoot, cwd: context.repo, env }), 0); + const foreignSource = join(context.root, "foreign-skill"); + mkdirSync(foreignSource); + writeFileSync(join(foreignSource, "SKILL.md"), "foreign\n"); + assert.equal(runSkillsInstallCli({ + args: ["uninstall", "--global", "--purge"], packageRoot, cwd: context.repo, env, + spawn: () => { + rmSync(join(codexSkills, "burnlist"), { recursive: true, force: true }); + symlinkSync(foreignSource, join(codexSkills, "burnlist"), process.platform === "win32" ? "junction" : "dir"); + return { status: 0 }; + }, + }), 0); + assert.equal(existsSync(join(claudeSkills, "burnlist")), false); + assert.equal(readlinkSync(join(codexSkills, "burnlist")), foreignSource); + assert.notEqual(readlinkSync(join(codexSkills, "burnlist")), packageSkill); + } finally { context.cleanup(); } +}); + +test("--agent scopes the install and uninstall to the requested agent", () => { + const context = fixture(); + try { + assert.equal(run(context, ["install", "--agent", "codex"]).status, 0); + assertLink(target(context, "codex")); + assert.equal(existsSync(target(context, "claude")), false); + assert.equal(run(context, ["uninstall", "--agent", "codex"]).status, 0); + assert.equal(existsSync(target(context, "codex")), false); + } finally { context.cleanup(); } +}); + +test("--dry-run reports the honest mode and exclude outcome without writing", () => { + const context = fixture(); + try { + const result = run(context, ["install", "--dry-run"]); + assert.equal(result.status, 0); + assert.match(result.stdout, /would link .*\.claude.*burnlist/u); + assert.match(result.stdout, /would link .*\.agents.*burnlist/u); + assert.match(result.stdout, /mode untracked \(local, \.git\/info\/exclude\); would write exclude entry/u); + const committed = run(context, ["install", "--commit", "--dry-run"]); + assert.equal(committed.status, 0); + assert.match(committed.stdout, /would copy .*mode committable \(portable copy; run git add to track\); no owned exclude entry to remove/u); + assert.equal(existsSync(join(context.repo, ".claude")), false); + assert.equal(existsSync(join(context.repo, ".agents")), false); + assert.doesNotMatch(exclude(context), /\/\.(?:claude|agents)\/skills\/burnlist/u); + } finally { context.cleanup(); } +}); + +test("unknown --agent values fail with valid choices", () => { + const context = fixture(); + try { + const result = run(context, ["install", "--agent", "cursor"]); + assert.equal(result.status, 1); + assert.match(result.stderr, /unknown --agent value: cursor\. Valid agents: codex, claude\./u); + } finally { context.cleanup(); } +}); diff --git a/src/cli/skills-install-cli.mjs b/src/cli/skills-install-cli.mjs new file mode 100644 index 0000000..6226c23 --- /dev/null +++ b/src/cli/skills-install-cli.mjs @@ -0,0 +1,132 @@ +import { spawnSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import { basename, dirname, resolve } from "node:path"; + +import { registerSkills, removeSnapshotManagedSkills, snapshotManagedSkills, unregisterSkills } from "./skills-register.mjs"; +import { withGlobalSkillsLock } from "./skills-install-lock.mjs"; + +const VALID_AGENTS = Object.freeze(["codex", "claude"]); + +function parseAgents(value) { + const agents = value.split(",").map((agent) => agent.trim()); + if (!agents.length || agents.some((agent) => !agent)) { + throw new Error("--agent requires codex, claude, or a comma-separated list of both"); + } + const unknown = agents.find((agent) => !VALID_AGENTS.includes(agent)); + if (unknown) throw new Error(`unknown --agent value: ${unknown}. Valid agents: codex, claude.`); + return [...new Set(agents)]; +} + +function parseSkillCommand(args) { + const command = args[0]; + if (!command) throw new Error("missing skill command"); + let global = false; + let dryRun = false; + let purge = false; + let commit = false; + let force = false; + let agents = VALID_AGENTS; + let agentSpecified = false; + for (let index = 1; index < args.length; index += 1) { + const argument = args[index]; + if (argument === "--global") global = true; + else if (argument === "--dry-run") dryRun = true; + else if (argument === "--commit") commit = true; + else if (argument === "--force") force = true; + else if (argument === "--purge") purge = true; + else if (argument === "--agent") { + const value = args[++index]; + if (!value || value.startsWith("--")) throw new Error("--agent requires codex, claude, or a comma-separated list of both"); + agents = parseAgents(value); + agentSpecified = true; + } else if (argument.startsWith("--agent=")) { + agents = parseAgents(argument.slice("--agent=".length)); + agentSpecified = true; + } else throw new Error(`unexpected argument: ${argument}`); + } + if (command === "install" && purge) throw new Error("--purge is only valid with uninstall"); + if (command === "uninstall" && commit) throw new Error("--commit is only valid with install"); + if (command === "uninstall" && force) throw new Error("--force is only valid with install"); + if (global && commit) throw new Error("--commit is only valid for per-repository skill installs"); + if (purge && !global) throw new Error("--purge requires --global"); + if (purge && agentSpecified) throw new Error("--purge removes the global package and must clean both agents; omit --agent"); + return { command, scope: global ? "global" : "repo", dryRun, purge, commit, force, agents }; +} + +function npmGlobalPrefix(packageRoot) { + let current = packageRoot; + while (dirname(current) !== current) { + if (basename(current) === "node_modules") { + const parent = dirname(current); + return basename(parent) === "lib" ? dirname(parent) : parent; + } + current = dirname(current); + } + throw new Error("Burnlist is not running from a global npm installation."); +} + +function purgeGlobalPackage({ packageRoot, env, error, spawn }) { + const prefix = npmGlobalPrefix(packageRoot); + const npm = process.platform === "win32" ? "npm.cmd" : "npm"; + const removal = spawn(npm, ["uninstall", "--global", "--prefix", prefix, "burnlist"], { + env, + shell: false, + stdio: "inherit", + }); + if (removal.error || removal.status !== 0) { + error("Burnlist: npm uninstall failed; checking global skill registrations for newly broken links."); + return removal.status ?? 1; + } + return 0; +} + +export function runSkillsInstallCli({ args, packageRoot, cwd = process.cwd(), env = process.env, log = console.log, warn = console.warn, error = console.error, spawn = spawnSync, remove }) { + try { + const options = parseSkillCommand(args); + const sourceRoot = resolve(packageRoot, "skills"); + if (options.command === "install") { + registerSkills({ sourceRoot, cwd, env, ...options, log }); + return 0; + } + if (options.command !== "uninstall") throw new Error(`unknown skill command: ${options.command}`); + if (!options.purge) { + const cleanup = unregisterSkills({ sourceRoot, cwd, env, ...options, log, warn }); + if (!cleanup.removed.length && cleanup.excludesRemoved) { + log(`Burnlist: ${options.dryRun ? "would remove" : "removed"} ${cleanup.excludesRemoved} owned local exclude entr${cleanup.excludesRemoved === 1 ? "y" : "ies"}.`); + } + if (!cleanup.removed.length && !cleanup.excludesRemoved) log("Burnlist: nothing installed to remove."); + return 0; + } + if (options.dryRun) { + log("Burnlist: would uninstall the global npm package burnlist."); + const planned = snapshotManagedSkills({ sourceRoot, scope: "global", cwd, env, agents: options.agents }); + for (const registration of planned) log(`Burnlist: would remove ${registration.source} -> ${registration.target}; global managed registration.`); + if (!planned.length) log("Burnlist: no global skill registrations would be removed."); + return 0; + } + npmGlobalPrefix(packageRoot); + return withGlobalSkillsLock(env, () => { + const snapshot = snapshotManagedSkills({ sourceRoot, scope: "global", cwd, env, agents: options.agents }); + const purgeStatus = purgeGlobalPackage({ packageRoot, env, error, spawn }); + if (purgeStatus !== 0) { + // npm can remove the package tree and still exit non-zero. Revalidate + // the pre-npm identities and only clean links whose source is now gone. + const dangling = snapshot.filter((registration) => registration.state === "link" && !existsSync(registration.source)); + const cleanup = removeSnapshotManagedSkills({ registrations: dangling, env, log, warn, remove }); + if (cleanup.removed.length) error(`Burnlist: npm uninstall failed after removing the package; removed now-broken global skill link(s): ${cleanup.removed.map(({ target }) => target).join(", ")}.`); + if (cleanup.failures.length) error(`Burnlist: npm uninstall failed and could not remove ${cleanup.failures.length} now-broken global skill link(s): ${cleanup.failures.map(({ target, error: cause }) => `${target} (${cause.message})`).join("; ")}`); + return purgeStatus; + } + const cleanup = removeSnapshotManagedSkills({ registrations: snapshot, env, log, warn, remove }); + if (!cleanup.removed.length && !cleanup.failures.length) log("Burnlist: nothing installed to remove."); + if (cleanup.failures.length) { + error(`Burnlist: npm uninstall succeeded, but could not remove ${cleanup.failures.length} global skill registration(s): ${cleanup.failures.map(({ target, error: cause }) => `${target} (${cause.message})`).join("; ")}`); + return 1; + } + return 0; + }); + } catch (cause) { + error(`Burnlist: ${cause.message}`); + return 1; + } +} diff --git a/src/cli/skills-install-cli.test.mjs b/src/cli/skills-install-cli.test.mjs new file mode 100644 index 0000000..2017797 --- /dev/null +++ b/src/cli/skills-install-cli.test.mjs @@ -0,0 +1,304 @@ +import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; + +const sourceRoot = resolve(fileURLToPath(new URL("../..", import.meta.url))); +const cli = join(sourceRoot, "bin", "burnlist.mjs"); +const skillSource = join(sourceRoot, "skills", "burnlist"); +const { BURNLIST_CLAUDE_SKILLS_DIR, BURNLIST_SKILLS_DIR, ...baseEnv } = process.env; + +function fixture() { + const root = mkdtempSync(join(tmpdir(), "burnlist-skills-install-cli-")); + const repo = join(root, "repo"); + const home = join(root, "home"); + mkdirSync(repo); + mkdirSync(home); + execFileSync("git", ["init", "--quiet"], { cwd: repo }); + return { root, repo, home, cleanup: () => rmSync(root, { recursive: true, force: true }) }; +} + +function run(context, args, env = {}) { + return spawnSync(process.execPath, [cli, ...args], { + cwd: context.repo, + encoding: "utf8", + env: { ...baseEnv, HOME: context.home, USERPROFILE: context.home, ...env }, + }); +} + +function target(context, agent, root = context.repo) { + return join(root, agent === "claude" ? ".claude" : ".agents", "skills", "burnlist"); +} + +function assertLink(path, source = skillSource) { + assert.equal(lstatSync(path).isSymbolicLink(), true); + assert.equal(resolve(dirname(path), readlinkSync(path)), source); +} + +function exclude(context) { return readFileSync(join(context.repo, ".git", "info", "exclude"), "utf8"); } +function gitStatus(context) { return execFileSync("git", ["status", "--porcelain"], { cwd: context.repo, encoding: "utf8" }); } +function gitCheckIgnore(context, path) { return execFileSync("git", ["check-ignore", "-v", "--", path], { cwd: context.repo, encoding: "utf8" }); } + +test("default repository install is local and excluded, then uninstall restores its exclude lines", () => { + const context = fixture(); + try { + const excludePath = join(context.repo, ".git", "info", "exclude"); + writeFileSync(excludePath, `${readFileSync(excludePath, "utf8")}# unrelated\n/custom/\n`); + const before = exclude(context); + const installed = run(context, ["install"]); + assert.equal(installed.status, 0); + assert.match(installed.stdout, /mode untracked \(local, \.git\/info\/exclude\); exclude entry written/u); + const claude = target(context, "claude"); + const codex = target(context, "codex"); + assertLink(claude); + assertLink(codex); + assert.match(exclude(context), /^\/\.claude\/skills\/burnlist$/mu); + assert.match(exclude(context), /^\/\.agents\/skills\/burnlist$/mu); + assert.equal(gitStatus(context), ""); + assert.match(gitCheckIgnore(context, ".claude/skills/burnlist"), /\.claude\/skills\/burnlist/u); + assert.match(gitCheckIgnore(context, ".agents/skills/burnlist"), /\.agents\/skills\/burnlist/u); + assert.equal(run(context, ["uninstall"]).status, 0); + assert.equal(existsSync(claude), false); + assert.equal(existsSync(codex), false); + assert.equal(exclude(context), before); + assert.equal(existsSync(dirname(claude)), false); + assert.equal(existsSync(dirname(codex)), false); + } finally { context.cleanup(); } +}); + +test("reinstall is idempotent and retains exact-source links", () => { + const context = fixture(); + try { + assert.equal(run(context, ["install"]).status, 0); + const firstLink = readlinkSync(target(context, "codex")); + const second = run(context, ["install"]); + assert.equal(second.status, 0); + assert.match(second.stdout, /kept .*burnlist/u); + assert.match(second.stdout, /mode untracked \(local, \.git\/info\/exclude\)/u); + assert.equal(readlinkSync(target(context, "codex")), firstLink); + assertLink(target(context, "claude")); + assert.equal((exclude(context).match(/^\/\.claude\/skills\/burnlist$/gmu) ?? []).length, 1); + assert.equal((exclude(context).match(/^\/\.agents\/skills\/burnlist$/gmu) ?? []).length, 1); + } finally { context.cleanup(); } +}); + +test("--commit installs portable marked copies that git can add, is idempotent, and uninstalls them", () => { + const context = fixture(); + try { + const first = run(context, ["install", "--commit"]); + assert.equal(first.status, 0); + assert.match(first.stdout, /mode committable \(portable copy; run git add to track\); no owned exclude entry to remove/u); + for (const agent of ["claude", "codex"]) { + const destination = target(context, agent); + assert.equal(lstatSync(destination).isDirectory(), true); + assert.equal(lstatSync(destination).isSymbolicLink(), false); + assert.equal(existsSync(join(destination, "SKILL.md")), true); + const marker = JSON.parse(readFileSync(join(destination, ".burnlist-managed.json"), "utf8")); + assert.deepEqual(marker, { managedBy: "burnlist", skill: "burnlist", mode: "commit", version: "0.0.2" }); + assert.equal(Object.hasOwn(marker, "sourceRelative"), false); + assert.doesNotMatch(JSON.stringify(marker), new RegExp(context.root.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"), "u")); + } + assert.doesNotMatch(exclude(context), /\/\.(?:claude|agents)\/skills\/burnlist/u); + assert.match(gitStatus(context), /\?\? \.agents\//u); + assert.match(gitStatus(context), /\?\? \.claude\//u); + const second = run(context, ["install", "--commit"]); + assert.equal(second.status, 0); + assert.match(second.stdout, /kept .*mode committable/u); + assert.equal(run(context, ["uninstall"]).status, 0); + assert.equal(existsSync(target(context, "claude")), false); + assert.equal(existsSync(target(context, "codex")), false); + } finally { context.cleanup(); } +}); + +test("uninstall leaves a foreign copy without the provenance marker untouched", () => { + const context = fixture(); + try { + assert.equal(run(context, ["install", "--commit", "--agent", "codex"]).status, 0); + const destination = target(context, "codex"); + rmSync(destination, { recursive: true, force: true }); + mkdirSync(destination); + writeFileSync(join(destination, "SKILL.md"), "foreign\n"); + const result = run(context, ["uninstall", "--agent", "codex"]); + assert.equal(result.status, 0); + assert.equal(lstatSync(destination).isDirectory(), true); + assert.match(result.stderr, /left .* untouched/u); + } finally { context.cleanup(); } +}); + +test("uninstall removes owned excludes for missing or foreign targets without touching them", () => { + const context = fixture(); + try { + assert.equal(run(context, ["install"]).status, 0); + const claude = target(context, "claude"); + const codex = target(context, "codex"); + rmSync(claude, { recursive: true, force: true }); + rmSync(codex, { recursive: true, force: true }); + mkdirSync(codex); + writeFileSync(join(codex, "SKILL.md"), "foreign\n"); + const result = run(context, ["uninstall"]); + assert.equal(result.status, 0); + assert.equal(lstatSync(codex).isDirectory(), true); + assert.doesNotMatch(exclude(context), /# burnlist-managed:skills@1\n\/\.(?:claude|agents)\/skills\/burnlist/u); + assert.match(result.stderr, /left .* untouched/u); + assert.match(result.stdout, /removed 2 owned local exclude entries/u); + assert.doesNotMatch(result.stdout, /nothing installed to remove/u); + } finally { context.cleanup(); } +}); + +test("default install in a non-git directory remains a local symlink and reports no exclude destination", () => { + const context = fixture(); + try { + rmSync(join(context.repo, ".git"), { recursive: true, force: true }); + const result = run(context, ["install", "--agent", "codex"]); + assert.equal(result.status, 0); + assertLink(target(context, "codex")); + assert.match(result.stdout, /mode symlink \(no git repo to exclude into\)/u); + } finally { context.cleanup(); } +}); + +test("--commit in a non-git directory installs a marked portable copy without git instructions", () => { + const context = fixture(); + try { + rmSync(join(context.repo, ".git"), { recursive: true, force: true }); + const installed = run(context, ["install", "--commit", "--agent", "codex"]); + const destination = target(context, "codex"); + assert.equal(installed.status, 0); + assert.doesNotMatch(installed.stdout, /git add/u); + assert.match(installed.stdout, /mode portable copy \(no git repo\)/u); + assert.equal(lstatSync(destination).isDirectory(), true); + assert.equal(lstatSync(destination).isSymbolicLink(), false); + assert.deepEqual(JSON.parse(readFileSync(join(destination, ".burnlist-managed.json"), "utf8")), { + managedBy: "burnlist", skill: "burnlist", mode: "commit", version: "0.0.2", + }); + const uninstalled = run(context, ["uninstall", "--agent", "codex"]); + assert.equal(uninstalled.status, 0); + assert.doesNotMatch(uninstalled.stdout, /git add/u); + assert.match(uninstalled.stdout, /mode portable copy \(no git repo\)/u); + assert.equal(existsSync(destination), false); + } finally { context.cleanup(); } +}); + +test("default install refuses a target already tracked by git instead of excluding it", () => { + const context = fixture(); + try { + const destination = target(context, "claude"); + mkdirSync(destination, { recursive: true }); + writeFileSync(join(destination, "SKILL.md"), "tracked\n"); + execFileSync("git", ["add", ".claude/skills/burnlist/SKILL.md"], { cwd: context.repo }); + const before = exclude(context); + const result = run(context, ["install"]); + assert.equal(result.status, 1); + assert.match(result.stderr, /already tracked by git; refusing to hide a tracked skill/u); + assert.equal(exclude(context), before); + } finally { context.cleanup(); } +}); + +test("--commit reports a content-file ignore even when the copy directory is not ignored", () => { + const context = fixture(); + try { + writeFileSync(join(context.repo, ".gitignore"), "*.md\n"); + const result = run(context, ["install", "--commit", "--agent", "codex"]); + assert.equal(result.status, 0); + assert.match(result.stdout, /mode still ignored \(portable copy; ignored by .*\.gitignore:1:\*\.md/u); + assert.match(gitCheckIgnore(context, ".agents/skills/burnlist/SKILL.md"), /\.gitignore/u); + assert.equal(spawnSync("git", ["check-ignore", "--", ".agents/skills/burnlist"], { cwd: context.repo }).status, 1); + } finally { context.cleanup(); } +}); + +test("default install refuses to downgrade a portable copy unless --force is explicit", () => { + const context = fixture(); + try { + assert.equal(run(context, ["install", "--commit", "--agent", "codex"]).status, 0); + const refused = run(context, ["install", "--agent", "codex"]); + assert.equal(refused.status, 1); + assert.match(refused.stderr, /would downgrade a committed copy.*pass --force/u); + assert.equal(lstatSync(target(context, "codex")).isSymbolicLink(), false); + const forced = run(context, ["install", "--force", "--agent", "codex"]); + assert.equal(forced.status, 0); + assertLink(target(context, "codex")); + } finally { context.cleanup(); } +}); + +test("--force refuses to downgrade a tracked portable copy", () => { + const context = fixture(); + try { + assert.equal(run(context, ["install", "--commit", "--agent", "codex"]).status, 0); + execFileSync("git", ["add", ".agents/skills/burnlist"], { cwd: context.repo }); + const forced = run(context, ["install", "--force", "--agent", "codex"]); + assert.equal(forced.status, 1); + assert.match(forced.stderr, /tracked portable copy; refusing to replace.*Run git rm/u); + assert.equal(lstatSync(target(context, "codex")).isSymbolicLink(), false); + } finally { context.cleanup(); } +}); + +test("uninstall removes empty skill parents but retains a parent containing a foreign entry", () => { + const context = fixture(); + try { + assert.equal(run(context, ["install"]).status, 0); + const codexSkills = dirname(target(context, "codex")); + mkdirSync(join(codexSkills, "foreign")); + writeFileSync(join(codexSkills, "foreign", "SKILL.md"), "foreign\n"); + assert.equal(run(context, ["uninstall"]).status, 0); + assert.equal(existsSync(join(context.repo, ".claude")), false); + assert.equal(lstatSync(codexSkills).isDirectory(), true); + assert.equal(existsSync(join(codexSkills, "foreign", "SKILL.md")), true); + } finally { context.cleanup(); } +}); + +test("global install and uninstall honor isolated skill-directory overrides", () => { + const context = fixture(); + try { + const claudeSkills = join(context.root, "claude-skills"); + const codexSkills = join(context.root, "codex-skills"); + const env = { BURNLIST_CLAUDE_SKILLS_DIR: claudeSkills, BURNLIST_SKILLS_DIR: codexSkills }; + const installed = run(context, ["install", "--global"], env); + assert.equal(installed.status, 0); + assert.match(installed.stdout, /global symlink \(no repo exclude\)/u); + assertLink(join(claudeSkills, "burnlist")); + assertLink(join(codexSkills, "burnlist")); + assert.equal(existsSync(join(context.home, ".claude", "skills", "burnlist")), false); + assert.equal(existsSync(join(context.home, ".agents", "skills", "burnlist")), false); + const uninstalled = run(context, ["uninstall", "--global"], env); + assert.equal(uninstalled.status, 0); + assert.match(uninstalled.stdout, /global symlink \(no repo exclude\)/u); + assert.equal(existsSync(join(claudeSkills, "burnlist")), false); + assert.equal(existsSync(join(codexSkills, "burnlist")), false); + assert.equal(lstatSync(claudeSkills).isDirectory(), true); + assert.equal(lstatSync(codexSkills).isDirectory(), true); + } finally { context.cleanup(); } +}); + +test("global install refuses foreign files, directories, and symlinks without touching them", () => { + for (const foreign of ["file", "directory", "symlink"]) { + const context = fixture(); + try { + const claudeSkills = join(context.root, "claude-skills"); + const codexSkills = join(context.root, "codex-skills"); + const destination = join(claudeSkills, "burnlist"); + const env = { BURNLIST_CLAUDE_SKILLS_DIR: claudeSkills, BURNLIST_SKILLS_DIR: codexSkills }; + mkdirSync(claudeSkills, { recursive: true }); + if (foreign === "file") writeFileSync(destination, "foreign\n"); + else if (foreign === "directory") mkdirSync(destination); + else { + const foreignSource = join(context.root, "foreign-burnlist"); + mkdirSync(foreignSource); + writeFileSync(join(foreignSource, "SKILL.md"), "foreign\n"); + symlinkSync(foreignSource, destination, process.platform === "win32" ? "junction" : "dir"); + } + + const result = run(context, ["install", "--global"], env); + assert.equal(result.status, 1); + assert.match(result.stderr, foreign === "symlink" + ? /already links to a different skill source/u + : /not a Burnlist-managed symlink or provenance-marked portable copy/u); + assert.equal(lstatSync(destination).isSymbolicLink(), foreign === "symlink"); + assert.equal(lstatSync(destination).isDirectory(), foreign === "directory"); + if (foreign === "file") assert.equal(readFileSync(destination, "utf8"), "foreign\n"); + if (foreign === "symlink") assert.notEqual(readlinkSync(destination), skillSource); + assert.equal(existsSync(join(codexSkills, "burnlist")), false); + } finally { context.cleanup(); } + } +}); diff --git a/src/cli/skills-install-git.mjs b/src/cli/skills-install-git.mjs new file mode 100644 index 0000000..3ad640b --- /dev/null +++ b/src/cli/skills-install-git.mjs @@ -0,0 +1,36 @@ +import { readdirSync } from "node:fs"; +import { join, relative } from "node:path"; + +import { gitProbe } from "./git-ignore.mjs"; + +function gitPath(repoRoot, path) { + return relative(repoRoot, path).replace(/\\/gu, "/"); +} + +export function trackedPathsInGit(repoRoot, path) { + const result = gitProbe(repoRoot, ["ls-files", "--", gitPath(repoRoot, path)]); + if (result.status !== 0) throw new Error(result.error?.message || result.stderr?.trim() || `could not determine whether ${gitPath(repoRoot, path)} is tracked`); + return result.stdout.split(/\r?\n/u).filter(Boolean); +} + +function contentPaths(target, paths = []) { + for (const entry of readdirSync(target, { withFileTypes: true })) { + const targetPath = join(target, entry.name); + if (entry.isDirectory()) contentPaths(targetPath, paths); + else paths.push(targetPath); + } + return paths; +} + +// Check files, not just the directory: a pattern such as *.md ignores SKILL.md +// without necessarily ignoring its parent directory. +export function ignoredSkillContent(repoRoot, registration, marker) { + // Commit mode dereferences source links. Inspect the published copy rather + // than the source tree so check-ignore sees every file git could add. + const targets = contentPaths(registration.target); + targets.push(join(registration.target, marker)); + const result = gitProbe(repoRoot, ["check-ignore", "-v", "--", ...targets.map((path) => gitPath(repoRoot, path))]); + if (result.status === 1) return undefined; + if (result.status !== 0) throw new Error(result.error?.message || result.stderr?.trim() || "could not determine whether skill content is ignored"); + return result.stdout.trim().split(/\r?\n/u)[0]; +} diff --git a/src/cli/skills-install-lock.mjs b/src/cli/skills-install-lock.mjs new file mode 100644 index 0000000..e613e54 --- /dev/null +++ b/src/cli/skills-install-lock.mjs @@ -0,0 +1,11 @@ +import { resolve } from "node:path"; + +import { withRepoStateLock } from "../server/repo-state.mjs"; + +// Every global operation shares a HOME-derived lock root, independently of +// agent selection and skill-directory overrides. +export function withGlobalSkillsLock(env, fn) { + const home = env.HOME || env.USERPROFILE; + if (!home) throw new Error("cannot lock global skill registrations because no user home directory is available"); + return withRepoStateLock(resolve(home, ".burnlist"), fn); +} diff --git a/src/cli/skills-install-transaction.mjs b/src/cli/skills-install-transaction.mjs new file mode 100644 index 0000000..9873d45 --- /dev/null +++ b/src/cli/skills-install-transaction.mjs @@ -0,0 +1,185 @@ +import { lstatSync, mkdirSync, mkdtempSync, renameSync, rmSync, rmdirSync } from "node:fs"; +import { dirname, join } from "node:path"; + +import { filesystemIdentity, quarantineTarget, removeQuarantinedTarget } from "./atomic-quarantine.mjs"; + +function lstatOrNull(path) { + try { return lstatSync(path); } catch (error) { + if (error.code === "ENOENT") return null; + throw error; + } +} + +function ensureDirectory(path, created) { + const missing = []; + let current = path; + while (!lstatOrNull(current)) { + missing.push(current); + const parent = dirname(current); + if (parent === current) throw new Error(`could not create skill directory: ${path}`); + current = parent; + } + for (const directory of missing.reverse()) { + mkdirSync(directory); + created.push(directory); + } +} + +function removeCreatedDirectories(created, failures) { + for (const directory of created.reverse()) { + try { rmdirSync(directory); } catch (error) { + if (error.code !== "ENOENT" && error.code !== "ENOTEMPTY") failures.push(`could not remove ${directory}: ${error.message}`); + } + } +} + +function cleanBackups(changes) { + const failures = []; + for (const { transaction } of changes) { + if (!transaction) continue; + try { rmSync(transaction, { recursive: true, force: true }); } catch (error) { + failures.push(`could not remove committed backup ${transaction}: ${error.message}`); + } + } + if (failures.length) throw new Error(`install committed, but backup cleanup failed: ${failures.join("; ")}`); +} + +function targetVacantForRestore(target, transaction) { + const occupied = quarantineTarget({ + target, + quarantined: join(transaction, "rollback-occupant"), + validate: () => false, + }); + return occupied.status === "missing"; +} + +function rollback(changes, createdDirectories, exclude, beforeRestore) { + const failures = []; + for (const { registration, backup, createdIdentity, transaction } of changes.reverse()) { + try { + // A newly-created target has no backup. Only remove it when it is still + // the exact filesystem object this transaction published; a replacement + // entry belongs to somebody else. + if (createdIdentity) { + const outcome = removeQuarantinedTarget({ target: registration.target, identity: createdIdentity }); + if (outcome.status === "foreign") { + failures.push(`${registration.target} occupied by a foreign object`); + // TODO(follow-up): crash-recovery sweep of orphaned quarantine dirs. + continue; + } + } + if (backup) { + beforeRestore?.(registration); + if (!targetVacantForRestore(registration.target, transaction)) { + failures.push(`${registration.target} occupied by a foreign object`); + // TODO(follow-up): crash-recovery sweep of orphaned quarantine dirs. + continue; + } + renameSync(backup, registration.target); + } + } catch (error) { + failures.push(`could not restore ${registration.target}: ${error.message}`); + continue; + } + if (transaction) { + try { rmSync(transaction, { recursive: true, force: true }); } catch (error) { + failures.push(`could not remove rollback backup ${transaction}: ${error.message}`); + } + } + } + try { exclude?.restore?.(); } catch (error) { + failures.push(`could not restore exclude file: ${error.message}`); + } + removeCreatedDirectories(createdDirectories, failures); + return failures; +} + +// Target replacements are reversible renames until all targets and the exclude +// update have committed. create() must publish new targets atomically and call +// onCreated immediately after publishing a missing target, before its dir fsync. +export function runInstallTransaction({ planned, revalidate, create, exclude, beforeMutation, beforeRestore, validateQuarantined }) { + const changes = []; + const createdDirectories = []; + try { + for (const registration of planned) { + if (registration.action === "keep") continue; + ensureDirectory(registration.targetRoot, createdDirectories); + beforeMutation?.(registration); + Object.assign(registration, revalidate(registration)); + if (registration.action === "keep") continue; + if (registration.state !== "missing") { + const transaction = mkdtempSync(join(registration.targetRoot, ".burnlist-skill-transaction-")); + const backup = join(transaction, "previous"); + try { + // Recheck after making the backup container and immediately before the rename. + Object.assign(registration, revalidate(registration)); + if (registration.action === "keep") { + rmSync(transaction, { recursive: true, force: true }); + } else if (registration.state === "missing") { + rmSync(transaction, { recursive: true, force: true }); + let recorded = false; + const onCreated = () => { + if (!recorded) { + changes.push({ registration, createdIdentity: filesystemIdentity(registration.target) }); + recorded = true; + } + }; + create(registration, onCreated); + onCreated(); + } else { + const quarantined = quarantineTarget({ + target: registration.target, + quarantined: backup, + validate: () => validateQuarantined?.(registration, backup) ?? targetStateAt(registration, backup), + }); + if (quarantined.status !== "quarantined") { + rmSync(transaction, { recursive: true, force: true }); + throw new Error(`${registration.target} changed before it could be replaced`); + } + changes.push({ registration, transaction, backup }); + create(registration, () => { + const change = changes.at(-1); + change.createdIdentity = filesystemIdentity(registration.target); + }); + const change = changes.at(-1); + if (!change.createdIdentity) change.createdIdentity = filesystemIdentity(registration.target); + } + } catch (error) { + if (!changes.some((change) => change.transaction === transaction)) { + try { rmSync(transaction, { recursive: true, force: true }); } catch (cleanupError) { + throw new AggregateError([error, cleanupError], `install failed: ${error.message}; could not remove transaction backup ${transaction}: ${cleanupError.message}`); + } + } + throw error; + } + } else { + let recorded = false; + const onCreated = () => { + if (!recorded) { + changes.push({ registration, createdIdentity: filesystemIdentity(registration.target) }); + recorded = true; + } + }; + create(registration, onCreated); + onCreated(); + } + } + if (exclude?.changed) exclude.write(); + exclude?.afterWrite?.(); + } catch (error) { + const failures = rollback(changes, createdDirectories, exclude, beforeRestore); + if (failures.length) { + throw new AggregateError([error], `install failed: ${error.message}; rollback incomplete: ${failures.join("; ")}`); + } + throw error; + } + // Backups are disposable only after the full transaction, including exclude write, commits. + cleanBackups(changes); +} + +function targetStateAt(registration, path) { + const stat = lstatOrNull(path); + if (!stat) return "missing"; + if (stat.isSymbolicLink()) return registration.state === "link" ? "link" : "foreign-link"; + return registration.state === "copy" && stat.isDirectory() ? "copy" : "foreign"; +} diff --git a/src/cli/skills-install-transaction.test.mjs b/src/cli/skills-install-transaction.test.mjs new file mode 100644 index 0000000..9a27e33 --- /dev/null +++ b/src/cli/skills-install-transaction.test.mjs @@ -0,0 +1,176 @@ +import assert from "node:assert/strict"; +import { lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; + +import { filesystemIdentity, sameFilesystemIdentity } from "./atomic-quarantine.mjs"; +import { runInstallTransaction } from "./skills-install-transaction.mjs"; + +// Force the wall clock to advance at least one tick. A freshly created +// object's modification time (mtime) is set fresh at creation, but its +// resolution is bounded by the clock; without this, two creations issued +// back to back could land in the same tick and make two genuinely different +// filesystem objects compare equal by mtime alone (on top of Linux already +// reusing inode numbers immediately after unlink). Real installs are never +// this fast twice in a row, so this only exists to make the test itself +// deterministic across platforms. +function waitForNextTick() { + const start = Date.now(); + while (Date.now() === start) { /* busy-wait for the clock to tick */ } +} + +function fixture() { + const root = mkdtempSync(join(tmpdir(), "burnlist-skills-transaction-")); + const targetRoot = join(root, "skills"); + mkdirSync(targetRoot); + const oldSource = join(root, "old"); + const newSource = join(root, "new"); + mkdirSync(oldSource); + mkdirSync(newSource); + return { root, targetRoot, oldSource, newSource, cleanup: () => rmSync(root, { recursive: true, force: true }) }; +} + +function link(source, target) { symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir"); } + +test("transaction keeps symlink backups until exclude commit and restores their type", () => { + const context = fixture(); + try { + const target = join(context.targetRoot, "burnlist"); + link(context.oldSource, target); + const registration = { target, targetRoot: context.targetRoot, state: "link", action: "link" }; + assert.throws(() => runInstallTransaction({ + planned: [registration], + revalidate: () => ({ state: "link", action: "link" }), + create: () => link(context.newSource, target), + exclude: { changed: true, write: () => {}, afterWrite: () => { throw new Error("exclude post-write failure"); } }, + }), /exclude post-write failure/u); + assert.equal(lstatSync(target).isSymbolicLink(), true); + assert.equal(readlinkSync(target), context.oldSource); + assert.deepEqual(readdirSync(context.targetRoot).filter((name) => name.startsWith(".burnlist-skill-transaction-")), []); + } finally { context.cleanup(); } +}); + +test("transaction preserves a foreign target that appears immediately before mutation", () => { + const context = fixture(); + try { + const first = join(context.targetRoot, "first"); + const raced = join(context.targetRoot, "raced"); + link(context.oldSource, first); + const planned = [ + { target: first, targetRoot: context.targetRoot, state: "link", action: "link" }, + { target: raced, targetRoot: context.targetRoot, state: "missing", action: "link" }, + ]; + assert.throws(() => runInstallTransaction({ + planned, + beforeMutation: (registration) => { if (registration.target === raced) writeFileSync(raced, "foreign\n"); }, + revalidate: (registration) => { + if (registration.target === raced && lstatSync(raced)) throw new Error("foreign target appeared"); + return { state: "link", action: "link" }; + }, + create: (registration) => link(context.newSource, registration.target), + }), /foreign target appeared/u); + assert.equal(readlinkSync(first), context.oldSource); + assert.equal(lstatSync(raced).isFile(), true); + } finally { context.cleanup(); } +}); + +test("second revalidation keeps an already-correct target without recreating it", () => { + const context = fixture(); + try { + const target = join(context.targetRoot, "burnlist"); + link(context.oldSource, target); + let checks = 0; + let created = false; + runInstallTransaction({ + planned: [{ target, targetRoot: context.targetRoot, state: "link", action: "link" }], + revalidate: () => { + checks += 1; + if (checks === 2) { + rmSync(target, { recursive: true, force: true }); + link(context.newSource, target); + return { state: "link", action: "keep" }; + } + return { state: "link", action: "link" }; + }, + create: () => { created = true; }, + }); + assert.equal(created, false); + assert.equal(readlinkSync(target), context.newSource); + } finally { context.cleanup(); } +}); + +test("transaction records a new target before a post-publish fsync failure", () => { + const context = fixture(); + try { + const target = join(context.targetRoot, "burnlist"); + const registration = { target, targetRoot: context.targetRoot, state: "missing", action: "link" }; + assert.throws(() => runInstallTransaction({ + planned: [registration], + revalidate: () => ({ state: "missing", action: "link" }), + create: (_, onCreated) => { + link(context.newSource, target); + onCreated(); + throw new Error("parent fsync failure"); + }, + }), /parent fsync failure/u); + assert.throws(() => lstatSync(target), { code: "ENOENT" }); + } finally { context.cleanup(); } +}); + +test("created-target rollback leaves a foreign entry that replaced it", () => { + const context = fixture(); + try { + const target = join(context.targetRoot, "burnlist"); + const registration = { target, targetRoot: context.targetRoot, state: "missing", action: "link" }; + let createdIdentity; + assert.throws(() => runInstallTransaction({ + planned: [registration], + revalidate: () => ({ state: "missing", action: "link" }), + create: (_, onCreated) => { + link(context.newSource, target); + createdIdentity = filesystemIdentity(target); + onCreated(); + rmSync(target, { recursive: true, force: true }); + // Inode numbers can be reused the instant a path is unlinked (this is + // routine on Linux/ext4), so raw {dev, ino} equality cannot be relied + // on to prove the replacement below is a distinct object — only the + // production identity check (dev + ino + mtimeMs) can. Force a clock + // tick so the replacement's mtimeMs is guaranteed to differ, then + // assert via the actual guard function used in production, not a + // platform-dependent assumption about inode allocation. + waitForNextTick(); + link(context.newSource, target); + const replacement = filesystemIdentity(target); + assert.equal(sameFilesystemIdentity(replacement, createdIdentity), false); + throw new Error("later failure"); + }, + }), /later failure/u); + assert.equal(lstatSync(target).isSymbolicLink(), true); + assert.equal(readlinkSync(target), context.newSource); + } finally { context.cleanup(); } +}); + +test("rollback leaves a foreign target that races into a restore vacancy", () => { + const context = fixture(); + try { + const target = join(context.targetRoot, "burnlist"); + link(context.oldSource, target); + const registration = { target, targetRoot: context.targetRoot, state: "link", action: "link" }; + assert.throws(() => runInstallTransaction({ + planned: [registration], + revalidate: () => ({ state: "link", action: "link" }), + create: (_, onCreated) => { + link(context.newSource, target); + onCreated(); + throw new Error("later failure"); + }, + beforeRestore: () => writeFileSync(target, "foreign\n"), + }), new RegExp(`rollback incomplete: ${target} occupied by a foreign object`, "u")); + assert.equal(lstatSync(target).isFile(), true); + assert.equal(readFileSync(target, "utf8"), "foreign\n"); + const transaction = readdirSync(context.targetRoot).find((name) => name.startsWith(".burnlist-skill-transaction-")); + assert.ok(transaction); + assert.equal(readlinkSync(join(context.targetRoot, transaction, "previous")), context.oldSource); + } finally { context.cleanup(); } +}); diff --git a/src/cli/skills-register.mjs b/src/cli/skills-register.mjs new file mode 100644 index 0000000..137113c --- /dev/null +++ b/src/cli/skills-register.mjs @@ -0,0 +1,383 @@ +import { cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, renameSync, rmSync, rmdirSync, symlinkSync } from "node:fs"; +import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; + +import { gitProbe } from "./git-ignore.mjs"; +import { addOwnedLocalExcludeText, fsyncDirectory, gitExcludePath, localExcludeTarget, removeOwnedLocalExcludeText, stageAtomicText, writeAtomicText } from "./local-exclude.mjs"; +import { filesystemIdentity, removeQuarantinedTarget } from "./atomic-quarantine.mjs"; +import { restoreExcludeSnapshot, snapshotExclude, writeGuardedExclude } from "./skills-exclude.mjs"; +import { ignoredSkillContent, trackedPathsInGit } from "./skills-install-git.mjs"; +import { withGlobalSkillsLock } from "./skills-install-lock.mjs"; +import { runInstallTransaction } from "./skills-install-transaction.mjs"; +import { withRepoStateLock } from "../server/repo-state.mjs"; + +export const TARGETS = Object.freeze({ + claude: Object.freeze({ + global: ({ env, home }) => resolve(env.BURNLIST_CLAUDE_SKILLS_DIR || join(home, ".claude", "skills")), + repo: ({ repoRoot }) => resolve(repoRoot, ".claude", "skills"), + }), + codex: Object.freeze({ + global: ({ env, home }) => resolve(env.BURNLIST_SKILLS_DIR || join(home, ".agents", "skills")), + repo: ({ repoRoot }) => resolve(repoRoot, ".agents", "skills"), + }), +}); + +const SKILL_NAME = /^[a-z0-9][a-z0-9-]*$/u; +const SKILL_EXCLUDE_MARKER = "burnlist-managed:skills@1"; +const COPY_MARKER = ".burnlist-managed.json"; + +function lstatOrNull(path) { + try { return lstatSync(path); } catch (error) { + if (error.code === "ENOENT") return null; + throw error; + } +} + +function linkedSource(path, sourceBase = dirname(path)) { return resolve(sourceBase, readlinkSync(path)); } + +function skillNames(sourceRoot) { + return readdirSync(sourceRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory() || entry.isSymbolicLink()) + .map((entry) => entry.name) + .sort() + .map((name) => { + if (!SKILL_NAME.test(name)) throw new Error(`unsafe skill folder name: ${name}`); + const source = resolve(sourceRoot, name); + if (!existsSync(join(source, "SKILL.md"))) throw new Error(`skill ${name} is missing SKILL.md`); + return { name, source }; + }); +} + +export function resolveRepoRoot(cwd = process.cwd()) { + const current = resolve(cwd); + const result = gitProbe(current, ["rev-parse", "--show-toplevel"]); + if (result.status !== 0) { + if (result.status === 128 && /not a git repository/iu.test(result.stderr ?? "")) return current; + const reason = result.error?.message || result.stderr?.trim() || `git exited with status ${result.status}`; + throw new Error(`could not resolve repository root for ${current}: ${reason}`); + } + const root = result.stdout.trim(); + if (!root) throw new Error(`could not resolve repository root for ${current}: git returned no worktree root`); + return resolve(root); +} + +function gitContext(cwd) { + const root = resolveRepoRoot(cwd); + const result = gitProbe(root, ["rev-parse", "--show-toplevel"]); + if (result.status === 0) return { root: resolve(result.stdout.trim()), git: true }; + if (result.status === 128 && /not a git repository/iu.test(result.stderr ?? "")) return { root, git: false }; + const reason = result.error?.message || result.stderr?.trim() || `git exited with status ${result.status}`; + throw new Error(`could not resolve repository root for ${root}: ${reason}`); +} + +export function registrationScope(args, env = process.env) { + const inlineScope = args.find((arg) => arg.startsWith("--scope=")); + if (inlineScope) { + const scope = inlineScope.slice("--scope=".length); + if (!scope) throw new Error("--scope requires global or repo"); + return scope; + } + const scopeIndex = args.indexOf("--scope"); + if (scopeIndex !== -1) { + const scope = args[scopeIndex + 1]; + if (!scope) throw new Error("--scope requires global or repo"); + return scope; + } + return env.npm_config_global === "true" || args.includes("--force-global") ? "global" : "repo"; +} + +function homeForGlobalTargets(env) { + const needsHome = !env.BURNLIST_CLAUDE_SKILLS_DIR || !env.BURNLIST_SKILLS_DIR; + const home = env.HOME || env.USERPROFILE; + if (needsHome && !home) throw new Error("cannot register agent skills because no user home directory is available"); + return home; +} + +export function targetRoots({ scope, cwd = process.cwd(), env = process.env, agents = Object.keys(TARGETS) }) { + if (!Object.hasOwn(TARGETS.claude, scope)) throw new Error(`unknown skill registration scope: ${scope}`); + const home = scope === "global" ? homeForGlobalTargets(env) : undefined; + const repoRoot = scope === "repo" ? resolveRepoRoot(cwd) : undefined; + return agents.map((agent) => { + const targets = TARGETS[agent]; + if (!targets) throw new Error(`unknown skill registration agent: ${agent}`); + return { agent, root: targets[scope]({ env, home, repoRoot }) }; + }); +} + +function registrations({ sourceRoot, scope, cwd, env, agents }) { + return targetRoots({ scope, cwd, env, agents }).flatMap(({ agent, root }) => skillNames(sourceRoot).map(({ name, source }) => ({ + agent, name, source, target: resolve(root, name), targetRoot: root, + }))); +} + +function copyMarker(registration, version) { + return { + managedBy: "burnlist", + skill: registration.name, + mode: "commit", + version, + }; +} + +function sourcePackageVersion(sourceRoot) { + const packageJson = JSON.parse(readFileSync(join(sourceRoot, "..", "package.json"), "utf8")); + if (typeof packageJson.version !== "string" || packageJson.version === "") { + throw new Error(`could not determine package version for portable skills in ${sourceRoot}`); + } + return packageJson.version; +} + +function isManagedCopy(registration, path = registration.target) { + const stat = lstatOrNull(path); + if (!stat || !stat.isDirectory() || stat.isSymbolicLink()) return false; + try { + const marker = JSON.parse(readFileSync(join(path, COPY_MARKER), "utf8")); + return marker?.managedBy === "burnlist" && marker?.skill === registration.name; + } catch { return false; } +} + +function targetState(registration, path = registration.target, sourceBase = dirname(path)) { + const stat = lstatOrNull(path); + if (!stat) return "missing"; + if (stat.isSymbolicLink()) return linkedSource(path, sourceBase) === registration.source ? "link" : "foreign-link"; + return isManagedCopy(registration, path) ? "copy" : "foreign"; +} + +function removeManagedTarget(registration, state, identity, remove) { + return removeQuarantinedTarget({ + target: registration.target, + identity, + validate: (quarantined) => targetState(registration, quarantined, dirname(registration.target)) === state, + remove, + }); +} + +export function snapshotManagedSkills({ sourceRoot, scope = "repo", cwd = process.cwd(), env = process.env, agents }) { + return registrations({ sourceRoot, scope, cwd, env, agents }).flatMap((registration) => { + const state = targetState(registration); + return state === "link" || state === "copy" ? [{ ...registration, state, identity: filesystemIdentity(registration.target) }] : []; + }); +} + +export function removeSnapshotManagedSkills({ registrations: snapshot, env = process.env, log = console.log, warn = console.warn, remove = rmSync }) { + const removed = []; + const failures = []; + for (const registration of snapshot) { + try { + // The package may already be gone, so use the pre-npm snapshot and only + // remove the exact object discovered before npm ran. + const outcome = removeManagedTarget(registration, registration.state, registration.identity, remove); + if (outcome.status !== "removed") { + warn(`Burnlist: left ${registration.target} untouched because it is no longer the exact managed registration discovered before purge.`); + continue; + } + removeEmptySkillParents(registration, homeForGlobalTargets(env)); + log(`Burnlist: removed ${registration.source} -> ${registration.target}; global managed registration.`); + removed.push(registration); + } catch (error) { + failures.push({ target: registration.target, error }); + } + } + return { removed, failures }; +} + +function assertInstallable(registration, desired, force) { + const state = targetState(registration); + if (state === "foreign-link") throw new Error(`${registration.target} already links to a different skill source (foreign symlink; refusing to overwrite it)`); + if (state === "foreign") throw new Error(`${registration.target} already exists and is not a Burnlist-managed symlink or provenance-marked portable copy; refusing to overwrite it`); + if (state === "copy" && desired === "link" && !force) { + throw new Error(`${registration.target} is a Burnlist-managed portable copy; default install would downgrade a committed copy to a symlink. Run burnlist uninstall first, or pass --force to proceed.`); + } + return { ...registration, state, action: state === desired ? "keep" : desired }; +} + +function copySkill(registration, version, onCreated) { + const stage = mkdtempSync(join(registration.targetRoot, ".burnlist-skill-")); + const payload = join(stage, registration.name); + try { + cpSync(registration.source, payload, { recursive: true, dereference: true, errorOnExist: true }); + writeAtomicText(join(payload, COPY_MARKER), `${JSON.stringify(copyMarker(registration, version), null, 2)}\n`); + renameSync(payload, registration.target); + onCreated?.(); + fsyncDirectory(registration.targetRoot); + } finally { rmSync(stage, { recursive: true, force: true }); } +} + +function createInstallTarget(registration, version, onCreated) { + if (registration.action === "link") { + symlinkSync(registration.source, registration.target, process.platform === "win32" ? "junction" : "dir"); + onCreated?.(); + } else copySkill(registration, version, onCreated); +} + +function assertUntrackedLocalInstall(repoRoot, registration) { + const tracked = trackedPathsInGit(repoRoot, registration.target); + if (!tracked.length) return; + if (targetState(registration) === "copy") { + throw new Error(`${registration.target} is a tracked portable copy; refusing to replace it with a local symlink. Run git rm or uninstall with the commit-aware workflow first.`); + } + throw new Error(`${localExcludeTarget(repoRoot, registration.target)} is already tracked by git; refusing to hide a tracked skill in .git/info/exclude. Use --commit only with a Burnlist-managed portable copy.`); +} + +function formatInstall(registration, dryRun, commit) { + const verb = registration.action === "keep" ? (dryRun ? "would keep" : "kept") + : registration.action === "link" ? (dryRun ? "would link" : "linked") : (dryRun ? "would copy" : "copied"); + const mode = commit + ? registration.exclude === "no git repository to exclude into" ? "portable copy (no git repo)" + : registration.gitIgnore ? `still ignored (portable copy; ignored by ${registration.gitIgnore})` + : "committable (portable copy; run git add to track)" + : registration.exclude === "no git repository to exclude into" ? "symlink (no git repo to exclude into)" + : "untracked (local, .git/info/exclude)"; + const exclude = registration.exclude === "no git repository to exclude into" ? "" : `; ${registration.exclude}`; + return `Burnlist: ${verb} ${registration.source} -> ${registration.target}; mode ${mode}${exclude}.`; +} + +function formatGlobal(registration, dryRun) { + const verb = registration.action === "keep" ? (dryRun ? "would keep" : "kept") : (dryRun ? "would link" : "linked"); + return `Burnlist: ${verb} ${registration.source} -> ${registration.target}; global symlink (no repo exclude).`; +} + +export function registerSkills({ sourceRoot, scope = "repo", cwd = process.cwd(), env = process.env, agents, dryRun = false, commit = false, force = false, log = console.log, stageAtomic = stageAtomicText, beforeTargetMutation, afterExcludeWrite }) { + if (scope === "global" && commit) throw new Error("--commit is only valid for per-repository skill installs"); + const context = scope === "repo" ? gitContext(cwd) : null; + const register = () => { + const desired = commit ? "copy" : "link"; + const registrationsForScope = registrations({ sourceRoot, scope, cwd, env, agents }); + if (scope === "repo" && context.git && !commit) { + for (const registration of registrationsForScope) { + assertUntrackedLocalInstall(context.root, registration); + } + } + const planned = registrationsForScope.map((registration) => assertInstallable(registration, desired, force)); + let excludePath; + let excludeBefore; + let excludeBeforeText; + let excludeAfter; + if (scope === "repo" && context.git) { + excludePath = gitExcludePath(context.root); + excludeBefore = snapshotExclude(excludePath); + excludeBeforeText = excludeBefore.kind === "missing" ? "" : excludeBefore.text; + excludeAfter = excludeBeforeText; + for (const registration of planned) { + const target = localExcludeTarget(context.root, registration.target); + const previous = excludeAfter; + excludeAfter = commit + ? removeOwnedLocalExcludeText(excludeAfter, target, SKILL_EXCLUDE_MARKER) + : addOwnedLocalExcludeText(excludeAfter, target, SKILL_EXCLUDE_MARKER) ?? excludeAfter; + registration.exclude = previous === excludeAfter + ? (commit ? "no owned exclude entry to remove" : "exclude entry already present") + : (commit + ? (dryRun ? "would remove owned exclude entry" : "owned exclude entry removed") + : (dryRun ? "would write exclude entry" : "exclude entry written")); + } + } + for (const registration of planned) registration.exclude ??= "no git repository to exclude into"; + if (!dryRun) { + const version = commit && planned.some((registration) => registration.action === "copy") ? sourcePackageVersion(sourceRoot) : undefined; + runInstallTransaction({ + planned, + revalidate: (registration) => { + if (scope === "repo" && context.git && !commit) assertUntrackedLocalInstall(context.root, registration); + return assertInstallable(registration, desired, force); + }, + validateQuarantined: (registration, quarantined) => targetState(registration, quarantined, dirname(registration.target)) === registration.state, + create: (registration, onCreated) => createInstallTarget(registration, version, onCreated), + beforeMutation: beforeTargetMutation, + exclude: excludePath && excludeBeforeText !== excludeAfter ? (() => { + let written; + return { + changed: true, + write: () => { written = writeGuardedExclude({ path: excludePath, before: excludeBefore, text: excludeAfter, stageAtomic }); }, + restore: () => restoreExcludeSnapshot({ path: excludePath, before: excludeBefore, written }), + afterWrite: commit ? () => { + for (const registration of planned) registration.gitIgnore = ignoredSkillContent(context.root, registration, COPY_MARKER); + afterExcludeWrite?.(); + } : afterExcludeWrite, + }; + })() : commit && context.git ? { + afterWrite: () => { + for (const registration of planned) registration.gitIgnore = ignoredSkillContent(context.root, registration, COPY_MARKER); + afterExcludeWrite?.(); + }, + } : afterExcludeWrite ? { afterWrite: afterExcludeWrite } : undefined, + }); + } + for (const registration of planned) log(scope === "global" ? formatGlobal(registration, dryRun) : formatInstall(registration, dryRun, commit)); + return planned; + }; + if (dryRun) return register(); + if (scope === "repo" && context.git) return withRepoStateLock(context.root, register); + return scope === "global" ? withGlobalSkillsLock(env, register) : register(); +} + +export function unregisterSkills({ sourceRoot, scope = "repo", cwd = process.cwd(), env = process.env, agents, dryRun = false, log = console.log, warn = console.warn }) { + const context = scope === "repo" ? gitContext(cwd) : null; + const unregister = () => { + const planned = registrations({ sourceRoot, scope, cwd, env, agents }); + const removed = []; + let excludesRemoved = 0; + let excludePath; + let excludeBefore; + let excludeAfter; + if (scope === "repo" && context.git) { + excludePath = gitExcludePath(context.root); + excludeBefore = snapshotExclude(excludePath); + excludeAfter = excludeBefore.kind === "missing" ? "" : excludeBefore.text; + } + for (const registration of planned) { + if (excludeAfter !== undefined) { + const previous = excludeAfter; + excludeAfter = removeOwnedLocalExcludeText(excludeAfter, localExcludeTarget(context.root, registration.target), SKILL_EXCLUDE_MARKER); + registration.exclude = previous === excludeAfter + ? "no owned exclude entry to remove" + : (dryRun ? "would remove owned exclude entry" : "owned exclude entry removed"); + if (previous !== excludeAfter) excludesRemoved += 1; + } + } + for (const registration of planned) { + const state = targetState(registration); + if (state !== "link" && state !== "copy") { + if (state !== "missing") warn(`Burnlist: left ${registration.target} untouched because it is not managed by this package.`); + continue; + } + if (!dryRun) { + const outcome = removeManagedTarget(registration, state, filesystemIdentity(registration.target), rmSync); + if (outcome.status !== "removed") { + warn(`Burnlist: left ${registration.target} untouched because it changed before removal.`); + continue; + } + removeEmptySkillParents(registration, scope === "repo" ? context.root : homeForGlobalTargets(env)); + } + const mode = scope === "global" ? "global symlink (no repo exclude)" + : state === "copy" ? registration.exclude === undefined ? "portable copy (no git repo)" : "committable (portable copy; run git add to track)" + : registration.exclude === undefined ? "symlink (no git repo to exclude into)" : "untracked (local, .git/info/exclude)"; + const exclude = scope === "global" || registration.exclude === undefined ? "" : `; ${registration.exclude}`; + log(`Burnlist: ${dryRun ? "would remove" : "removed"} ${registration.source} -> ${registration.target}; mode ${mode}${exclude}.`); + removed.push(registration); + } + if (!dryRun && excludePath && (excludeBefore.kind === "missing" ? "" : excludeBefore.text) !== excludeAfter) { + writeGuardedExclude({ path: excludePath, before: excludeBefore, text: excludeAfter }); + } + return { removed, excludesRemoved }; + }; + if (dryRun) return unregister(); + if (scope === "repo" && context.git) return withRepoStateLock(context.root, unregister); + return scope === "global" ? withGlobalSkillsLock(env, unregister) : unregister(); +} + +function isWithin(parent, child) { + const pathFromParent = relative(parent, child); + return pathFromParent === "" || (pathFromParent !== ".." && !pathFromParent.startsWith(`..${sep}`) && !isAbsolute(pathFromParent)); +} + +function removeEmptySkillParents(registration, boundary) { + const stop = boundary && isWithin(boundary, registration.targetRoot) ? resolve(boundary) : registration.targetRoot; + let current = registration.targetRoot; + while (current !== stop) { + try { rmdirSync(current); } catch (error) { + if (error.code === "ENOENT") { current = dirname(current); continue; } + if (error.code === "ENOTEMPTY") return; + throw error; + } + current = dirname(current); + } +} diff --git a/src/cli/skills-register.test.mjs b/src/cli/skills-register.test.mjs new file mode 100644 index 0000000..187153b --- /dev/null +++ b/src/cli/skills-register.test.mjs @@ -0,0 +1,301 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { chmodSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; +import { withGlobalSkillsLock } from "./skills-install-lock.mjs"; +import { registerSkills, resolveRepoRoot } from "./skills-register.mjs"; + +const repoRoot = resolve(fileURLToPath(new URL("../..", import.meta.url))); +const registerScript = join(repoRoot, "scripts", "register-skills.mjs"); +const unregisterScript = join(repoRoot, "scripts", "unregister-skills.mjs"); +const source = join(repoRoot, "skills", "burnlist"); +const { BURNLIST_CLAUDE_SKILLS_DIR, BURNLIST_SKILLS_DIR, ...baseEnv } = process.env; + +function fixture() { + const root = mkdtempSync(join(tmpdir(), "burnlist-skills-register-")); + const repo = join(root, "repo"); + const home = join(root, "home"); + mkdirSync(repo); + mkdirSync(home); + return { root, repo, home, cleanup: () => rmSync(root, { recursive: true, force: true }) }; +} + +function run(script, context, args = [], env = {}) { + return execFileSync(process.execPath, [script, ...args], { + cwd: context.repo, + encoding: "utf8", + env: { ...baseEnv, HOME: context.home, USERPROFILE: context.home, ...env }, + }); +} + +function linkedTo(path, expected = source) { + assert.equal(lstatSync(path).isSymbolicLink(), true); + assert.equal(resolve(dirname(path), readlinkSync(path)), expected); +} + +test("global dry-run describes Claude and Codex targets", () => { + const context = fixture(); + try { + const output = run(registerScript, context, ["--force-global", "--dry-run"]); + assert.ok(output.includes(join(context.home, ".claude", "skills", "burnlist"))); + assert.ok(output.includes(join(context.home, ".agents", "skills", "burnlist"))); + assert.equal(lstatOrNull(join(context.home, ".claude", "skills", "burnlist")), null); + assert.equal(lstatOrNull(join(context.home, ".agents", "skills", "burnlist")), null); + } finally { context.cleanup(); } +}); + +test("repo dry-run describes both agent targets at the worktree root", () => { + const context = fixture(); + try { + execFileSync("git", ["init", "--quiet"], { cwd: context.repo }); + const nested = join(context.repo, "nested", "work"); + mkdirSync(nested, { recursive: true }); + const output = execFileSync(process.execPath, [registerScript, "--dry-run"], { + cwd: nested, + encoding: "utf8", + env: { ...baseEnv, HOME: context.home, USERPROFILE: context.home }, + }); + const worktreeRoot = realpathSync(context.repo); + assert.ok(output.includes(join(worktreeRoot, ".claude", "skills", "burnlist"))); + assert.ok(output.includes(join(worktreeRoot, ".agents", "skills", "burnlist"))); + } finally { context.cleanup(); } +}); + +test("each global override affects only its matching agent and remains idempotent", () => { + for (const { override, overridden, defaultTarget } of [ + { + override: "BURNLIST_CLAUDE_SKILLS_DIR", + overridden: "claude-skills", + defaultTarget: [".agents", "skills"], + }, + { + override: "BURNLIST_SKILLS_DIR", + overridden: "codex-skills", + defaultTarget: [".claude", "skills"], + }, + ]) { + const context = fixture(); + try { + const target = join(context.root, overridden); + const otherTarget = join(context.home, ...defaultTarget); + const env = { [override]: target }; + run(registerScript, context, ["--force-global"], env); + linkedTo(join(target, "burnlist")); + linkedTo(join(otherTarget, "burnlist")); + const output = run(registerScript, context, ["--force-global"], env); + assert.match(output, /kept .*burnlist/u); + run(unregisterScript, context, ["--force-global"], env); + assert.equal(lstatOrNull(join(target, "burnlist")), null); + assert.equal(lstatOrNull(join(otherTarget, "burnlist")), null); + } finally { context.cleanup(); } + } +}); + +test("global registration uses the shared global skill lock", () => { + const context = fixture(); + try { + const env = { + ...baseEnv, + HOME: context.home, + USERPROFILE: context.home, + BURNLIST_CLAUDE_SKILLS_DIR: join(context.root, "override-claude"), + BURNLIST_SKILLS_DIR: join(context.root, "override-codex"), + }; + assert.throws(() => withGlobalSkillsLock(env, () => { + assert.equal(lstatSync(join(context.home, ".burnlist", ".local", "burnlist", ".lock")).isDirectory(), true); + return registerSkills({ + sourceRoot: join(repoRoot, "skills"), scope: "global", cwd: context.repo, env, agents: ["codex"], log: () => {}, + }); + }), /Repo state is locked by pid/u); + } finally { context.cleanup(); } +}); + +test("repo registration creates both agent links at a temporary worktree root and is idempotent", () => { + const context = fixture(); + try { + execFileSync("git", ["init", "--quiet"], { cwd: context.repo }); + run(registerScript, context); + const claude = join(context.repo, ".claude", "skills", "burnlist"); + const codex = join(context.repo, ".agents", "skills", "burnlist"); + linkedTo(claude); + linkedTo(codex); + const output = run(registerScript, context, ["--scope=repo"]); + assert.match(output, /kept .*burnlist/u); + linkedTo(claude); + linkedTo(codex); + } finally { context.cleanup(); } +}); + +test("registration rolls every target and the exclude file back when its exclude write fails", () => { + const context = fixture(); + try { + execFileSync("git", ["init", "--quiet"], { cwd: context.repo }); + const options = { + sourceRoot: join(repoRoot, "skills"), cwd: context.repo, + env: { ...baseEnv, HOME: context.home, USERPROFILE: context.home }, log: () => {}, + }; + registerSkills(options); + const excludePath = join(context.repo, ".git", "info", "exclude"); + const beforeExclude = readFileSync(excludePath, "utf8"); + assert.throws( + () => registerSkills({ ...options, commit: true, stageAtomic: () => { throw new Error("injected exclude failure"); } }), + /injected exclude failure/u, + ); + linkedTo(join(context.repo, ".claude", "skills", "burnlist")); + linkedTo(join(context.repo, ".agents", "skills", "burnlist")); + assert.equal(readFileSync(excludePath, "utf8"), beforeExclude); + } finally { context.cleanup(); } +}); + +test("exclude rollback preserves the original filesystem object type and mode", () => { + for (const type of ["file", "symlink"]) { + const context = fixture(); + try { + execFileSync("git", ["init", "--quiet"], { cwd: context.repo }); + const excludePath = join(context.repo, ".git", "info", "exclude"); + const original = "# original exclude\n/original/\n"; + let external; + if (type === "symlink") { + external = join(context.root, "external-exclude"); + writeFileSync(external, original); + rmSync(excludePath); + symlinkSync(external, excludePath); + } else { + writeFileSync(excludePath, original); + chmodSync(excludePath, 0o644); + } + assert.throws(() => registerSkills({ + sourceRoot: join(repoRoot, "skills"), cwd: context.repo, agents: ["codex"], + env: { ...baseEnv, HOME: context.home, USERPROFILE: context.home }, log: () => {}, + afterExcludeWrite: () => { throw new Error("injected post-exclude failure"); }, + }), /injected post-exclude failure/u); + assert.equal(readFileSync(excludePath, "utf8"), original); + if (type === "symlink") assert.equal(readlinkSync(excludePath), external); + else assert.equal(lstatSync(excludePath).mode & 0o777, 0o644); + } finally { context.cleanup(); } + } +}); + +test("commit ignore checks the dereferenced published skill copy", () => { + const context = fixture(); + try { + execFileSync("git", ["init", "--quiet"], { cwd: context.repo }); + writeFileSync(join(context.repo, ".gitignore"), "*.md\n"); + const packageRoot = join(context.root, "package"); + const skills = join(packageRoot, "skills"); + const skill = join(skills, "burnlist"); + const linked = join(context.root, "linked-content"); + mkdirSync(skill, { recursive: true }); + mkdirSync(linked); + writeFileSync(join(packageRoot, "package.json"), '{"version":"1.0.0"}\n'); + writeFileSync(join(skill, "SKILL.md"), "skill\n"); + writeFileSync(join(linked, "ignored.md"), "ignored\n"); + symlinkSync(linked, join(skill, "linked"), process.platform === "win32" ? "junction" : "dir"); + const logs = []; + registerSkills({ + sourceRoot: skills, cwd: context.repo, agents: ["codex"], commit: true, + env: { ...baseEnv, HOME: context.home, USERPROFILE: context.home }, log: (line) => logs.push(line), + }); + assert.match(logs.join("\n"), /still ignored .*\*\.md/u); + } finally { context.cleanup(); } +}); + +test("revalidation keeps a target that becomes correct immediately before mutation", () => { + const context = fixture(); + try { + const target = join(context.repo, ".agents", "skills", "burnlist"); + const logs = []; + let inode; + const planned = registerSkills({ + sourceRoot: join(repoRoot, "skills"), cwd: context.repo, + env: { ...baseEnv, HOME: context.home, USERPROFILE: context.home }, agents: ["codex"], + log: (line) => logs.push(line), + beforeTargetMutation: () => { + symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir"); + inode = lstatSync(target).ino; + }, + }); + assert.equal(planned[0].action, "keep"); + assert.equal(lstatSync(target).ino, inode); + linkedTo(target); + assert.match(logs.join("\n"), /kept .*burnlist/u); + } finally { context.cleanup(); } +}); + +test("registration refuses foreign files and directories without overwriting them", () => { + for (const foreign of ["file", "directory"]) { + const context = fixture(); + try { + const target = join(context.repo, ".claude", "skills", "burnlist"); + mkdirSync(dirname(target), { recursive: true }); + if (foreign === "file") writeFileSync(target, "foreign\n"); + else mkdirSync(target); + assert.throws( + () => run(registerScript, context), + (error) => String(error.stderr).includes("not a Burnlist-managed symlink"), + ); + assert.equal(lstatSync(target).isDirectory(), foreign === "directory"); + assert.equal(lstatOrNull(join(context.repo, ".agents", "skills", "burnlist")), null); + } finally { context.cleanup(); } + } +}); + +test("registration refuses a symlink to a different skill source without replacing it", () => { + const context = fixture(); + try { + const target = join(context.repo, ".claude", "skills", "burnlist"); + const foreignSource = join(context.root, "foreign-burnlist"); + mkdirSync(dirname(target), { recursive: true }); + mkdirSync(foreignSource); + writeFileSync(join(foreignSource, "SKILL.md"), "foreign\n"); + symlinkSync(foreignSource, target, process.platform === "win32" ? "junction" : "dir"); + assert.throws( + () => run(registerScript, context), + (error) => String(error.stderr).includes("already links to a different skill source"), + ); + linkedTo(target, foreignSource); + assert.equal(lstatOrNull(join(context.repo, ".agents", "skills", "burnlist")), null); + } finally { context.cleanup(); } +}); + +test("repository root resolution fails closed for operational git errors", () => { + const context = fixture(); + try { + assert.throws( + () => resolveRepoRoot(join(context.root, "missing-directory")), + /could not resolve repository root/u, + ); + } finally { context.cleanup(); } +}); + +test("unregister removes only exact managed symlinks and preserves foreign entries", () => { + const context = fixture(); + try { + run(registerScript, context); + const claudeTarget = join(context.repo, ".claude", "skills", "burnlist"); + const codexTarget = join(context.repo, ".agents", "skills", "burnlist"); + const foreignTarget = join(context.repo, ".agents", "skills", "foreign"); + unlinkSync(codexTarget); + mkdirSync(codexTarget); + writeFileSync(join(codexTarget, "SKILL.md"), "foreign\n"); + symlinkSync(source, foreignTarget, process.platform === "win32" ? "junction" : "dir"); + run(unregisterScript, context); + assert.equal(lstatOrNull(claudeTarget), null); + assert.equal(lstatSync(codexTarget).isDirectory(), true); + assert.equal(lstatSync(foreignTarget).isSymbolicLink(), true); + assert.equal(lstatOrNull(dirname(claudeTarget)), null); + assert.equal(lstatSync(dirname(codexTarget)).isDirectory(), true); + } finally { context.cleanup(); } +}); + +function lstatOrNull(path) { + try { + return lstatSync(path); + } catch (error) { + if (error.code === "ENOENT") return null; + throw error; + } +} diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..7425c37 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.astro/ + +# Root .gitignore has a broad `docs/` pattern; re-include our docs content collection +!src/content/docs/ diff --git a/website/astro.config.mjs b/website/astro.config.mjs new file mode 100644 index 0000000..da791d4 --- /dev/null +++ b/website/astro.config.mjs @@ -0,0 +1,64 @@ +// @ts-check +import { defineConfig } from 'astro/config'; +import starlight from '@astrojs/starlight'; +import sitemap from '@astrojs/sitemap'; + +export default defineConfig({ + site: 'https://burnlist.dev', + integrations: [ + sitemap(), + starlight({ + title: 'Burnlist', + description: + 'A repo-local burndown tracker with a read-only observer dashboard and declarative Ovens.', + favicon: '/favicon.svg', + head: [ + { tag: 'meta', attrs: { property: 'og:title', content: 'Burnlist' } }, + { + tag: 'meta', + attrs: { + property: 'og:description', + content: + 'A repo-local burndown tracker with a read-only observer dashboard and declarative Ovens.', + }, + }, + { tag: 'meta', attrs: { property: 'og:type', content: 'website' } }, + { tag: 'meta', attrs: { name: 'twitter:card', content: 'summary' } }, + ], + customCss: ['./src/styles/custom.css'], + components: { Header: './src/components/DocsHeader.astro' }, + social: [ + { + icon: 'github', + label: 'GitHub', + href: 'https://github.com/layoutit/burnlist', + }, + ], + sidebar: [ + { + label: 'Getting Started', + items: [{ slug: 'getting-started' }, { slug: 'install' }], + }, + { + label: 'Concepts', + items: [{ slug: 'lifecycle' }], + }, + { + label: 'Reference', + items: [{ slug: 'cli' }, { slug: 'dashboard' }], + }, + { + label: 'Ovens', + items: [ + { slug: 'ovens', label: 'Overview' }, + { slug: 'ovens/checklist' }, + { slug: 'ovens/differential-testing' }, + { slug: 'ovens/streaming-diff' }, + { slug: 'ovens/performance-tracing' }, + { slug: 'ovens/visual-parity' }, + ], + }, + ], + }), + ], +}); diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..dcbb3cd --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,6296 @@ +{ + "name": "burnlist-website", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "burnlist-website", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@astrojs/sitemap": "^3.7.3", + "@astrojs/starlight": "^0.38.2", + "@layoutit/polycss": "^0.2.8", + "astro": "^6.1.1", + "sharp": "^0.34.5" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/compiler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-4.0.0.tgz", + "integrity": "sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.1.tgz", + "integrity": "sha512-5phcroT/vmOOrYuuAxtkbPixy5hePtlz9i8K4OeDv3dNK6/UQRuXPOSRTxIOBbUY5Sonw2UaxjbuVc43Mcir6Q==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.1.1", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.1.tgz", + "integrity": "sha512-jPVNIqTvk+yKviikszv/Y1U4jGUSKpp/Nw48QZV4qjWgp70j4Lkq3lhSDRbWwCfgKvEyO9GHuVbV1dM2WYXy1w==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.10.1", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-5.0.6.tgz", + "integrity": "sha512-4dKe0ZMmqujofPNDHahzClkwinn9f8jHPcaXcgdGvPAlboD2mjzkUCofli2cBnxYAkdfhC6d50gBJ8i/cH8gHw==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "7.1.2", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.16.0", + "es-module-lexer": "^2.0.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "piccolore": "^0.1.3", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "astro": "^6.0.0" + } + }, + "node_modules/@astrojs/mdx/node_modules/@astrojs/internal-helpers": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.9.1.tgz", + "integrity": "sha512-1pWuARqYom/TzuU3+0ZugsTrKlUydWKuULmDqSMTuonY+9IRDUEGKX/8PXQ1nBxRq3w85uGtd9q9SXfqEldMIQ==", + "license": "MIT", + "dependencies": { + "picomatch": "^4.0.4" + } + }, + "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.1.2.tgz", + "integrity": "sha512-caXZ4Dc2St2dW8luEg22GlP0gupLdztCTQE4EzZOxW1pqWXz9mbeJEuHUkgDYcKWW8tjIHkydYDhWLVoxJ327Q==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.9.1", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "js-yaml": "^4.1.1", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.0", + "smol-toml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/prism": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.3.tgz", + "integrity": "sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==", + "license": "MIT", + "dependencies": { + "sitemap": "^9.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^4.3.6" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.38.5", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.38.5.tgz", + "integrity": "sha512-35xLSOtZDAMAilHG2zAEZoJ4AaPb+doYOvxuuRTAnmIBSOvujffOAHv3/rr6W/LJtkhBU38PjRDJ4i8QT1uGVw==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "^7.1.1", + "@astrojs/mdx": "^5.0.4", + "@astrojs/sitemap": "^3.7.2", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.42.0", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.3", + "hast-util-select": "^6.0.4", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "i18next": "^23.11.5", + "js-yaml": "^4.1.1", + "klona": "^2.0.6", + "magic-string": "^0.30.21", + "mdast-util-directive": "^3.1.0", + "mdast-util-to-markdown": "^2.1.2", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0", + "rehype": "^13.0.2", + "rehype-format": "^5.0.1", + "remark-directive": "^4.0.0", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" + }, + "peerDependencies": { + "astro": "^6.0.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz", + "integrity": "sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.4.0", + "dset": "^3.1.4", + "is-docker": "^4.0.0", + "is-wsl": "^3.1.1", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@clack/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz", + "integrity": "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.7.0.tgz", + "integrity": "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.3", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "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/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.42.0.tgz", + "integrity": "sha512-MN11+9nfmaC7sYu2BZJXAXqwkBRt8t1xTSqP+Ti1NfTEskgl6xUnzDxoaiQkg0BMzpglA0pys4dpDKquP/cyIw==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.42.0.tgz", + "integrity": "sha512-XtkPm+941Uta7Y+81Acv+OA/20F1NJmJhCX6UYGKpqEIGqplNh3PTOhcURp6tcruhlzJcWcvpWy6Oigz3SrjqA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.42.0" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.42.0.tgz", + "integrity": "sha512-PMKey/kLmewttAHQezL+Y5Fx3vVssfDi3+FJOYQQS2mXP3tQspFELtKKAfsXfmSXdToZYgwoO69HJndqfE+09g==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.42.0", + "shiki": "^4.0.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.42.0.tgz", + "integrity": "sha512-l59lUx8fq1v5g6SpmbDjiU0+7IdfbiWnAyRmtTVSpfhyq+nZMN4UcmYyu2b9Mynhzt7Gr+O+cXyEPDNb2AVWVQ==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.42.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", + "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/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@layoutit/polycss": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@layoutit/polycss/-/polycss-0.2.8.tgz", + "integrity": "sha512-3mk0b8YaWQSvDuhlnVzjrdMWXCQr/S2jo3zBf6kbZVXAPBTTsDBmTGqf2JETSZCyyrq2bq50CIYEmm16/cC3ww==", + "license": "MIT", + "dependencies": { + "@layoutit/polycss-core": "^0.2.8" + } + }, + "node_modules/@layoutit/polycss-core": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@layoutit/polycss-core/-/polycss-core-0.2.8.tgz", + "integrity": "sha512-52qCWyQxF8Nug0evrjg1gtWcz7p5RcMe2zEZLJt4VwQgge5sA8nK2iZ3kIc27qsimYksKf0U9fuuwvz75LwphA==", + "license": "MIT" + }, + "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/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "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" + ], + "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" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.5.2.tgz", + "integrity": "sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==", + "license": "MIT" + }, + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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/@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/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "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/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/astro": { + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/astro/-/astro-6.4.8.tgz", + "integrity": "sha512-KK5lX90uU9EeVaTjINyj3sy9/NFXVa59aowaqbWBDDKLXZh4rr7GwIaCFYVetE22MJtsCNFerQXn0vlCLmpP/Q==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^4.0.0", + "@astrojs/internal-helpers": "0.10.0", + "@astrojs/markdown-remark": "7.2.0", + "@astrojs/telemetry": "3.3.2", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^1.1.1", + "devalue": "^5.8.1", + "diff": "^8.0.3", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.27.3", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.1.1", + "jsonc-parser": "^3.3.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.4", + "rehype": "^13.0.2", + "semver": "^7.7.4", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "svgo": "^4.0.1", + "tinyclip": "^0.1.12", + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.4", + "unist-util-visit": "^5.1.0", + "unstorage": "^1.17.5", + "vfile": "^6.0.3", + "vite": "^7.3.2", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" + }, + "bin": { + "astro": "bin/astro.mjs" + }, + "engines": { + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.42.0.tgz", + "integrity": "sha512-aiTePi2Cn0mJPYWZSzP1GcxCinX9mNtJyCCshVVPSg1yRwM7ADvFJOx0FnS440M9t65hp8JH//dc2qr22Bm4ag==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.42.0" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" + } + }, + "node_modules/astro/node_modules/@astrojs/internal-helpers": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.0.tgz", + "integrity": "sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.1.1", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" + } + }, + "node_modules/astro/node_modules/@astrojs/markdown-remark": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.0.tgz", + "integrity": "sha512-+YxmVQu1Bd+MFfSzjq1rOJvD9+nIOJzz5YIIhdIH01RrxRkKbyKoEgyIqP3yv51MhzMDgd79QaPv+kCVPT8vHw==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.10.0", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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/bcp-47": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.1.tgz", + "integrity": "sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "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/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/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/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "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==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "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/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/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/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "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/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "license": "MIT" + }, + "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/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "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/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.42.0.tgz", + "integrity": "sha512-V5DtJLEKuj4wf9O6IRtPtRObkMVy2ggR+S0MdjrTw6m58krZnDioyhW1si3Y04c5YPeooP4nd85Yq9NwEVHS4g==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.42.0", + "@expressive-code/plugin-frames": "^0.42.0", + "@expressive-code/plugin-shiki": "^0.42.0", + "@expressive-code/plugin-text-markers": "^0.42.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/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.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-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.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-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.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-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.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-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "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/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "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/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "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-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "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-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.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-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "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-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "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/p-limit": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", + "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.1.tgz", + "integrity": "sha512-POWdiIPmsUPGwb4FeQ4OBg46aqmcInSWe45CKDsGHiOBiVQM9chqfQTuqhuTzcg2Vz9faTI65at0KkVyVEiCHw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.7.0.tgz", + "integrity": "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", + "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-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "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/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "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-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "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": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.42.0.tgz", + "integrity": "sha512-8rp/1YMEVVSYbtz+bFBx+uSx3vA4i4T8RwRm5Q/IWbucQnnQqQ0hDqtmKOr8tv+59Cik6cu5aH3WPo0I7csuTA==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.42.0" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-4.0.0.tgz", + "integrity": "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^4.0.0", + "unified": "^11.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-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "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", + "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", + "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/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^24.9.2", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/esm/cli.js" + }, + "engines": { + "node": ">=20.19.5", + "npm": ">=10.8.2" + } + }, + "node_modules/smol-toml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "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/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "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/svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz", + "integrity": "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz", + "integrity": "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz", + "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "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/unifont": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ofetch": "^1.5.1", + "ohash": "^2.0.11" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^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-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.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-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.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-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.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/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "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==", + "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/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "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/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "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..a6a7004 --- /dev/null +++ b/website/package.json @@ -0,0 +1,21 @@ +{ + "name": "burnlist-website", + "type": "module", + "version": "0.0.1", + "private": true, + "description": "Burnlist documentation and landing page.", + "license": "MIT", + "engines": { "node": ">=22.12.0" }, + "scripts": { + "dev": "astro dev", + "build": "astro build && node scripts/generate-llms-txt.mjs", + "preview": "astro preview" + }, + "dependencies": { + "@astrojs/sitemap": "^3.7.3", + "@astrojs/starlight": "^0.38.2", + "@layoutit/polycss": "^0.2.8", + "astro": "^6.1.1", + "sharp": "^0.34.5" + } +} diff --git a/website/public/favicon.svg b/website/public/favicon.svg new file mode 100644 index 0000000..1f4793b --- /dev/null +++ b/website/public/favicon.svg @@ -0,0 +1,6 @@ + + Burnlist + A triangle containing a smaller nested triangle. + + + diff --git a/website/public/robots.txt b/website/public/robots.txt new file mode 100644 index 0000000..82275b4 --- /dev/null +++ b/website/public/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Sitemap: https://burnlist.dev/sitemap-index.xml diff --git a/website/scripts/generate-llms-txt.mjs b/website/scripts/generate-llms-txt.mjs new file mode 100644 index 0000000..c62d7a6 --- /dev/null +++ b/website/scripts/generate-llms-txt.mjs @@ -0,0 +1,100 @@ +import { cp, mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises'; +import { execFileSync } from 'node:child_process'; +import path from 'node:path'; + +import { buildSkillMarkdown, FALLBACK_CLI_HELP } from './skill-content.mjs'; + +const SITE_URL = 'https://burnlist.dev'; +const docsDirectory = path.resolve('src/content/docs'); +const outputDirectory = path.resolve('dist/docs'); +const cliEntrypoint = path.resolve('../bin/burnlist.mjs'); + +function readCliHelp() { + try { + return execFileSync(process.execPath, [cliEntrypoint, '--help'], { encoding: 'utf8' }).trimEnd(); + } catch { + // Standalone build without the repo-root CLI available (e.g. a published + // package checkout): fall back to the last-known-accurate help text. + return FALLBACK_CLI_HELP; + } +} + +async function walkDir(directory) { + const entries = await readdir(directory, { withFileTypes: true }); + const files = await Promise.all( + entries.map((entry) => { + const entryPath = path.join(directory, entry.name); + return entry.isDirectory() ? walkDir(entryPath) : [entryPath]; + }), + ); + return files.flat(); +} + +function extractFrontmatter(source) { + const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/); + if (!match) return { title: undefined, body: source }; + const title = match[1].match(/^title:\s*['"]?(.+?)['"]?\s*$/m)?.[1]; + return { title, body: source.slice(match[0].length) }; +} + +function getSlug(file) { + return path + .relative(docsDirectory, file) + .replace(/\.(md|mdx)$/, '') + .split(path.sep) + .join('/'); +} + +const files = (await walkDir(docsDirectory)).filter((file) => /\.mdx?$/.test(file)); +const documents = await Promise.all( + files.map(async (file) => { + const source = await readFile(file, 'utf8'); + const { title, body } = extractFrontmatter(source); + return { slug: getSlug(file), title: title ?? getSlug(file), body, source: file }; + }), +); + +const header = [ + '# Burnlist', + '', + '> A repo-local burndown tracker with a read-only observer dashboard and declarative Ovens. MIT licensed.', + '', +]; +const index = documents.map(({ slug, title }) => `- [${title}](${SITE_URL}/docs/${slug}.md)`); +const footer = ['', '- GitHub: https://github.com/layoutit/burnlist', '- License: MIT', '']; +const skillMarkdown = buildSkillMarkdown({ documents, siteUrl: SITE_URL, cliHelp: readCliHelp() }); + +// Atomic publish: stage the complete output in a sibling temp dir, then rename +// each artifact into place so a reader never observes a partial file or tree. +const distDirectory = path.resolve('dist'); +const stagingDirectory = path.join(distDirectory, `.llms-staging-${process.pid}`); +await rm(stagingDirectory, { recursive: true, force: true }); +await mkdir(stagingDirectory, { recursive: true }); + +try { + const stagedDocs = path.join(stagingDirectory, 'docs'); + await Promise.all( + documents.map(async ({ slug, source }) => { + const destination = path.join(stagedDocs, `${slug}.md`); + await mkdir(path.dirname(destination), { recursive: true }); + await cp(source, destination); + }), + ); + + await writeFile(path.join(stagingDirectory, 'llms.txt'), [...header, ...index, ...footer].join('\n')); + await writeFile( + path.join(stagingDirectory, 'llms-full.txt'), + [...header, ...documents.flatMap(({ title, body }) => [`## ${title}`, '', body.trim(), '']), ...footer].join('\n'), + ); + await writeFile(path.join(stagingDirectory, 'skill.md'), skillMarkdown); + + await rm(outputDirectory, { recursive: true, force: true }); + await rename(stagedDocs, outputDirectory); + await rename(path.join(stagingDirectory, 'llms.txt'), path.join(distDirectory, 'llms.txt')); + await rename(path.join(stagingDirectory, 'llms-full.txt'), path.join(distDirectory, 'llms-full.txt')); + await rename(path.join(stagingDirectory, 'skill.md'), path.join(distDirectory, 'skill.md')); +} finally { + await rm(stagingDirectory, { recursive: true, force: true }); +} + +console.log(`Generated llms.txt, llms-full.txt, skill.md, and ${documents.length} documentation file(s).`); diff --git a/website/scripts/skill-content.mjs b/website/scripts/skill-content.mjs new file mode 100644 index 0000000..204856b --- /dev/null +++ b/website/scripts/skill-content.mjs @@ -0,0 +1,151 @@ +/** + * Builds the content of skill.md — a single, self-contained Burnlist skill + * document served at the site root (https://burnlist.dev/skill.md) so an + * agent pointed at that URL has everything it needs: what Burnlist is, how + * to install it, the full CLI surface, the lifecycle, the five ovens, and a + * listing of every doc page. Kept accurate to the real CLI help output and + * the docs it is generated alongside; do not describe behavior the CLI does + * not have (no loop feature, no componentization, no work execution). + */ + +// Fallback used only if the sibling CLI can't be spawned at build time (see +// generate-llms-txt.mjs, which prefers the live `burnlist --help` output). +export const FALLBACK_CLI_HELP = `Burnlist + +Usage: + burnlist [--port ] [--scan-root ] + burnlist --plan --check + burnlist --plan --digest + burnlist --close-completed [--scan-root ] + burnlist --stamp + burnlist differential-testing validate + burnlist differential-testing validate-bundle + burnlist differential-testing schema + burnlist differential-testing sdk + burnlist streaming-diff ... + burnlist hooks [install|uninstall|status] [--agent codex,claude] [--untracked] (bare defaults to status) + burnlist oven ... + burnlist new [--repo ] + burnlist show [#] [--repo ] + burnlist ready [--repo ] + burnlist start [--repo ] + burnlist close [--repo ] + burnlist burn [--check] [--repo ] + burnlist register [path] + burnlist unregister [path] + burnlist roots [--prune] + burnlist init [path] [--track] + burnlist install [--global] [--commit] [--force] [--agent codex,claude] [--dry-run] + burnlist uninstall [--global] [--agent codex,claude] [--dry-run] [--purge] + +Options: + --auto-port Try the next available loopback port. + --host Bind host; loopback is required by default. + --state-dir Override ignored dashboard observer state. + --ovens-dir Override launch-repository custom Oven storage only. + --runs-dir Override Run snapshot storage. + --oven-data Bind one Oven to a read-only normalized JSON payload. + --global Install or uninstall skills in the user home directory. + --commit Per-repository install: copy portable skills for git commit. + --force Permit install to replace a Burnlist-managed portable copy with a symlink. + --agent Restrict skill install or uninstall to codex, claude, or both. + --dry-run Print skill link or portable-copy plans without writing them. + --purge With uninstall --global only, also remove the global npm package. + --version, -v Print the installed Burnlist version. + --help, -h Show this help.`; + +export function buildSkillMarkdown({ documents, siteUrl, cliHelp = FALLBACK_CLI_HELP }) { + const docLinks = documents.map(({ slug, title }) => `- [${title}](${siteUrl}/docs/${slug}.md)`).join('\n'); + + return `# Burnlist skill + +> Point an agent at ${siteUrl}/skill.md for a complete, self-contained Burnlist skill: what it is, how to install it, its full CLI surface, its lifecycle, and its five ovens. + +## What Burnlist is + +Burnlist is a real-time, non-invasive tracker for agents. A Burnlist stores work in a repo-local, shrinking Markdown checklist (\`notes/burnlists///burnlist.md\`) and renders live progress in a local, read-only observer dashboard. It has zero runtime dependencies (pure Node built-ins, ES modules, Node >= 18). + +Burnlist owns task state — not implementation, testing, or delivery. **It does not execute your work or drive your agents.** It is built for planning and observing an agent's work; the agent (or a domain-specific skill) does the actual work, and reports progress back into the Burnlist. + +## Install + +Install and hooks are independent steps — installing one does not install the other. + +### 1. Install the CLI + +\`\`\`sh +npm install --global burnlist +\`\`\` + +Installs the \`burnlist\` command. Its npm \`postinstall\` step also registers the bundled agent skill globally (Claude Code under \`~/.claude/skills\`, Codex under \`~/.agents/skills\`) so the skill is available immediately. + +### 2. (Reinstall or customize) the agent skill + +\`\`\`sh +burnlist install [--global] [--commit] [--force] [--agent codex,claude] [--dry-run] +\`\`\` + +Without \`--global\`, this registers the skill for the current repository only (an untracked local link by default, or a portable copy for git with \`--commit\`). With \`--global\`, it (re)registers the skill in the user's home directory, same as the npm postinstall step. \`--agent codex,claude\` restricts which agent(s) get the skill; \`--dry-run\` prints the plan without writing. + +### 3. Install Streaming Diff hooks (optional, separate feature) + +\`\`\`sh +burnlist hooks install --agent codex,claude +burnlist hooks status +burnlist hooks uninstall --agent codex,claude +\`\`\` + +Merges local Streaming Diff commands into \`.codex/hooks.json\` and/or \`.claude/settings.json\`, preserving existing entries. This is unrelated to the skill install above; installing the skill does not install hooks, and installing hooks does not install the skill. + +### Uninstall + +\`\`\`sh +burnlist uninstall [--global] [--agent codex,claude] [--dry-run] [--purge] +\`\`\` + +\`--purge\` (global only) also removes the global npm package. + +## CLI surface + +Authoritative output of \`burnlist --help\`: + +\`\`\`text +${cliHelp} +\`\`\` + +## Lifecycle + +A Burnlist's state is its location. The whole folder moves through four lifecycle directories, and each item burns down through five verbs, in order: + +| Verb | CLI command | Meaning | +| --- | --- | --- | +| new | \`burnlist new\` | Create a draft Burnlist. | +| ready | \`burnlist ready \` | Mark a plan ready. | +| start | \`burnlist start \` | Move ready to inprogress. | +| burn | \`burnlist burn \` | Complete one active item. | +| close | \`burnlist close \` | Close a completed Burnlist. | + +Folders: \`notes/burnlists/{draft,ready,inprogress,completed}//\`. \`goal.md\` is the stable contract (Goal, Guardrails, Proof Authority, Ordering Intent, Stop Conditions, Handoff); \`burnlist.md\` is the hot, shrinking task state (an ordered Active Checklist and a terse Completed ledger); \`completed.md\` is optional durable per-burn history for humans. Run \`burnlist --plan --check\` to validate and \`burnlist --stamp\` to generate the mechanical timestamp used in a completion ledger line. + +## The five ovens + +An Oven is a named, declarative, non-executable recipe for a Burn — data, never code. Five built-in, read-only ovens ship with Burnlist: + +- **Checklist** tracks the active work queue and progress. +- **Differential Testing** provides aligned reference-versus-candidate series, optional aggregate telemetry, and exact-first evidence. +- **Streaming Diff** surfaces recently published, session-scoped pre-to-post diff cards read from a local feed. +- **Performance Tracing** renders retained browser-output timing evidence — frame pacing, budget checks, and slow steps — from a project-owned trace run. +- **Visual Parity** compares trusted reference and candidate frames as isolated render passes, gating each render domain on calibrated channel, mean-delta, and changed-pixel bounds. + +Author and inspect ovens with \`burnlist oven \`. Custom ovens live in ignored, repo-scoped \`.local/burnlist/ovens/\` state. + +## Documentation + +${docLinks} + +## Source + +- GitHub: https://github.com/layoutit/burnlist +- License: MIT +`; +} diff --git a/website/src/components/DocsHeader.astro b/website/src/components/DocsHeader.astro new file mode 100644 index 0000000..411ec78 --- /dev/null +++ b/website/src/components/DocsHeader.astro @@ -0,0 +1,171 @@ +--- +import config from 'virtual:starlight/user-config'; +import Search from 'virtual:starlight/components/Search'; +import ThemeSelect from 'virtual:starlight/components/ThemeSelect'; + +const pathname = Astro.url.pathname; +const topLinks = [ + { href: '/getting-started', label: 'Docs' }, + { href: '/cli', label: 'CLI' }, + { href: '/ovens', label: 'Ovens' }, + { href: '/dashboard', label: 'Dashboard' }, +]; +const isActive = (href: string) => pathname.startsWith(href); +const siteTitle = + typeof config.title === 'string' ? config.title : (config.title?.en ?? 'Burnlist'); +--- + +
+
+
+ {siteTitle} + +
+ + +
+
+ + + + diff --git a/website/src/content.config.ts b/website/src/content.config.ts new file mode 100644 index 0000000..6a7b7a0 --- /dev/null +++ b/website/src/content.config.ts @@ -0,0 +1,7 @@ +import { defineCollection } from 'astro:content'; +import { docsLoader } from '@astrojs/starlight/loaders'; +import { docsSchema } from '@astrojs/starlight/schema'; + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), +}; diff --git a/website/src/content/docs/cli.mdx b/website/src/content/docs/cli.mdx new file mode 100644 index 0000000..593c523 --- /dev/null +++ b/website/src/content/docs/cli.mdx @@ -0,0 +1,99 @@ +--- +title: CLI reference +description: Every Burnlist command, grouped by task. +--- + +Run `burnlist --help` for the authoritative, installed surface. + +## Dashboard + +```sh +burnlist [--port ] [--scan-root ] +``` + +Launches the loopback observer dashboard. + +## Plan protocol + +```sh +burnlist --plan --check +burnlist --plan --digest +burnlist --close-completed [--scan-root ] +burnlist --stamp +``` + +`--check` validates the active queue and completed ledger. `--digest` prints a completion digest once the active queue is empty. `--close-completed` adds a digest when needed and moves empty in-progress Burnlists to completed. `--stamp` prints a local ISO timestamp for completion records. + +## Lifecycle + +These commands act on the current repository by default, or on an explicit `--repo ` target. The lifecycle order is `new` → `ready` → `start` → `burn` → `close`. + +| Command | Description | +| --- | --- | +| `burnlist new [--repo ]` | Create a new Burnlist. | +| `burnlist show [#] [--repo ]` | Show a Burnlist or one of its items. | +| `burnlist ready [--repo ]` | Move a Burnlist to ready. | +| `burnlist start [--repo ]` | Move a ready Burnlist into progress. | +| `burnlist burn [--check] [--repo ]` | Complete a Burnlist item; `--check` validates it. | +| `burnlist close [--repo ]` | Close a completed Burnlist. | + +## Registry + +```sh +burnlist register [path] +burnlist unregister [path] +burnlist roots [--prune] +burnlist init [path] [--track] +``` + +`init` scaffolds `notes/burnlists/{draft,ready,inprogress,completed}/` and registers the root. `register` adds an existing repository. `roots` lists registered roots with health, while `--prune` drops missing ones. `unregister` removes a registered path. + +## Ovens + +```sh +burnlist oven ... +``` + +Author and inspect Ovens. (`burnlist oven help` lists a few more subcommands, such as `fork`.) See [Ovens](/ovens). + +## Skills + +```sh +burnlist install [--global] [--agent codex,claude] [--commit] [--force] [--dry-run] +burnlist uninstall [--global] [--agent codex,claude] [--dry-run] [--purge] +``` + +Registers (or removes) the bundled Burnlist skill for Claude Code and/or Codex, per-repository by default or globally with `--global`. Independent of Hooks below — install one, the other, or both. See [Install](/install). + +## Hooks + +```sh +burnlist hooks [--agent codex,claude] [--untracked] +``` + +Independent of Skills above. See [Install](/install). + +## Differential Testing + +```sh +burnlist differential-testing ... +``` + +## Streaming Diff + +```sh +burnlist streaming-diff ... +``` + +## Options + +| Option | Description | +| --- | --- | +| `--auto-port` | Try the next available loopback port. | +| `--host ` | Bind host; loopback is required by default. | +| `--state-dir ` | Override ignored dashboard observer state. | +| `--ovens-dir ` | Override launch-repository custom Oven storage only. | +| `--runs-dir ` | Override Run snapshot storage. | +| `--oven-data ` | Bind one Oven to a read-only normalized JSON payload. | +| `--version`, `-v` | Print the version. | +| `--help`, `-h` | Print help. | diff --git a/website/src/content/docs/dashboard.mdx b/website/src/content/docs/dashboard.mdx new file mode 100644 index 0000000..b6fdf42 --- /dev/null +++ b/website/src/content/docs/dashboard.mdx @@ -0,0 +1,50 @@ +--- +title: Dashboard +description: The read-only observer over your Burnlist lifecycle folders. +--- + +## A read-only observer + +The dashboard scans lifecycle folders and refreshes automatically. It is read-only: it never mutates canonical task state (`burnlist.md`, the lifecycle folders, or the registry). All canonical writes are CLI-only. The dashboard's only write surface is a pair of explicit, token-gated, loopback-only controller actions — New Oven and Run Burn — that record local files under `.local/burnlist/` and never touch canonical state (see [below](#new-oven-and-run-burn)). + +## Running + +```sh +burnlist +burnlist --port 4510 +``` + +It binds only to loopback hosts by default and prints its local URL. An occupied port is a hard error unless you pass `--auto-port`. It scans: + +```text +notes/burnlists/{draft,ready,inprogress,completed}/*/burnlist.md +``` + +To force a scope, pass comma-separated roots: + +```sh +burnlist --scan-root /path/to/repo,/path/to/another-repo +``` + +## What it parses from `burnlist.md` + +The dashboard parses top metadata, `Goal: ./goal.md`, `## Active Checklist`, and the terse `## Completed` ledger. The completed ledger is the only completion source of truth; Tasks KPI equals completed plus remaining. Do not add chart metadata, telemetry, or progress fields to a Burnlist. + +## The project registry + +The dashboard observes Burnlists across a machine-local registry of repository roots (`~/.burnlist/roots.json`) unioned with the current repository, so one dashboard can cover every registered project. Registration is always explicit: the CLI is the only writer, and nothing auto-registers. + +- `burnlist init [path]` — for a new repository, scaffold the lifecycle folders, git-ignore that state locally (or use `--track` to commit it), and register the root. +- `burnlist register [path]` — register an existing repository that already has Burnlists, with no scaffolding. +- `burnlist unregister [path]` — remove a repository root. +- `burnlist roots [--prune]` — list registered roots with health: healthy, empty, missing, or unreadable. `--prune` drops only missing roots. + +A Burnlist in an unregistered repository is still visible when the dashboard launches inside that repository, but it is not visible in the global landing until `init` or `register`. Observation spans all registered repositories, but mutating verbs (`--close-completed` and lifecycle moves) act only on the current repository. + +## New Oven and Run Burn + +New Oven and Run Burn are explicit, user-controlled local controller surfaces. They write local controller records under `.local/burnlist/` by default: custom Ovens under `.local/burnlist/ovens/` and immutable Run snapshots under `.local/burnlist/runs/`. They do not change canonical task state, execute instructions, or start an agent. See [Ovens](/ovens). + +## Local state + +Dashboard observer state, custom Ovens, and Run snapshots live under `.local/burnlist/`. Keep `notes/burnlists/` and `.local/burnlist/` ignored unless you deliberately want to share task state. diff --git a/website/src/content/docs/getting-started.mdx b/website/src/content/docs/getting-started.mdx new file mode 100644 index 0000000..06c5424 --- /dev/null +++ b/website/src/content/docs/getting-started.mdx @@ -0,0 +1,29 @@ +--- +title: 'Getting Started' +description: 'Install Burnlist and run your first burndown.' +--- + +Burnlist is a repo-local burndown tracker with a read-only observer dashboard and declarative **Ovens**. It stores work in a shrinking Markdown checklist and renders live progress; it owns task state, not implementation, tests, or delivery. It is built for planning and observing an agent's work — but it never executes your work or drives your agents. + +Burnlist has zero runtime dependencies: it uses pure Node built-ins and ES modules, and runs on Node.js 18 or newer. + +## Explore the CLI + +Use the built-in help to see every available command, then create a burnlist in your repository. + +```sh +burnlist --help +burnlist new +``` + +The core workflow moves a burnlist item through its lifecycle: + +`new` → `ready` → `start` → `burn` → `close` + +Useful commands include `burnlist show `, `burnlist ready `, `burnlist start `, `burnlist burn `, and `burnlist close `. Run `burnlist --help` for the full list. + +You can also launch the dashboard server with `burnlist`, create a workspace with `burnlist init`, manage registered roots with `burnlist register`, `burnlist unregister`, and `burnlist roots`, and manage hooks with `burnlist hooks `. + +## Coming next + +Upcoming sections cover the [CLI](/cli), [Ovens](/ovens), and the [Dashboard](/dashboard). Burnlist also includes `burnlist oven `, `burnlist differential-testing ...`, and `burnlist streaming-diff ...` commands. diff --git a/website/src/content/docs/install.mdx b/website/src/content/docs/install.mdx new file mode 100644 index 0000000..abac8f1 --- /dev/null +++ b/website/src/content/docs/install.mdx @@ -0,0 +1,74 @@ +--- +title: Install +description: Install the Burnlist CLI, the agent skill, and the Streaming Diff hooks. +--- + +Burnlist requires Node.js 18 or newer. It has zero runtime dependencies: it uses pure Node built-ins and ES modules. + +Burnlist has two independent, optional integrations on top of the CLI: the **skill**, which teaches an agent to create and execute Burnlists, and the **hooks**, which capture edit activity for Streaming Diff. Installing one does not install, require, or remove the other — install one, the other, or both. + +## Install the CLI + +```sh +npm install --global burnlist +``` + +The global package installs the `burnlist` command. Its npm `postinstall` step also globally registers the bundled agent skill for both agents (Claude Code under `~/.claude/skills`, Codex under `~/.agents/skills`) so the skill is available immediately. Run `burnlist` from any project to open the dashboard; it binds to loopback by default and prints its local URL. + +## Give your agent the skill + +One agent skill named `burnlist` owns the full Burnlist lifecycle — creation, hardening, execution, and maintenance. Burnlist owns task state; your project (or a domain skill) owns implementation, tests, and delivery. + +Two ways to get the skill in front of an agent: + +- **Point the agent at the URL.** Paste `https://burnlist.dev/skill.md` to your coding agent (Claude Code or Codex) and it fetches a complete, self-contained skill document — no local setup needed. +- **Register it locally.** The global npm install already registers the skill for both agents (see above). To (re)install per-repository, customize which agent gets it, or reinstall after removal: + + ```sh + burnlist install [--global] [--agent codex,claude] [--commit] [--force] [--dry-run] + ``` + + Without `--global`, this registers the skill for the current repository only (an untracked local symlink by default, or a portable copy for git with `--commit`): + + | Agent | Per-repository target | Global target (`--global`) | + | --- | --- | --- | + | Claude Code | `.claude/skills/burnlist` | `~/.claude/skills/burnlist` | + | Codex | `.agents/skills/burnlist` | `~/.agents/skills/burnlist` | + + `--agent codex,claude` restricts which agent(s) get the skill (default: both); `--dry-run` prints the plan without writing. Remove a registration with the symmetric `burnlist uninstall [--global] [--agent codex,claude] [--dry-run] [--purge]` — `--purge` (global only) also removes the global npm package. + +Ask your agent to create a Burnlist for a goal or continue an existing one. The skill owns that workflow, while the CLI provides the dashboard and protocol helpers. + +## The hooks system (optional, independent of the skill) + +Streaming Diff hooks are optional and installed per-agent, separately from the skill above. You can install them for Codex, Claude, or both: + +```sh +burnlist hooks install --agent codex,claude +burnlist hooks status +burnlist hooks uninstall --agent codex,claude +``` + +Installation merges local Streaming Diff commands into `.codex/hooks.json` and `.claude/settings.json`, preserving existing hook entries. Hooks use the portable `burnlist` command from `PATH`. + +The agent remains responsible for any first-run hook trust or consent prompt; Burnlist only writes configuration. `burnlist hooks status` reports whether each config is tracked (shared) or local. `--agent codex,claude` selects agents, and `--untracked` is available for untracked handling. + +## From a source checkout (optional) + +```sh +npm install +npm run build:dashboard +npm run verify +``` + +`verify` checks source, contract tests, and the leak scan. + +## Uninstall + +```sh +burnlist uninstall [--global] [--agent codex,claude] [--dry-run] [--purge] +``` + +Removes Burnlist-managed skill registrations in the matching scope (per-repository by default, or `--global`). `--purge` requires `uninstall --global`, targets both agents, and also runs `npm uninstall --global` to remove the package itself. Hooks are removed separately with `burnlist hooks uninstall` (above). + +Next: [Getting started](/getting-started), [CLI reference](/cli), and [Dashboard](/dashboard). diff --git a/website/src/content/docs/lifecycle.mdx b/website/src/content/docs/lifecycle.mdx new file mode 100644 index 0000000..5fe44cc --- /dev/null +++ b/website/src/content/docs/lifecycle.mdx @@ -0,0 +1,74 @@ +--- +title: Lifecycle +description: How a Burnlist moves from draft to completed — and how an item burns down. +--- + +A Burnlist's state **is** its location. Burnlist moves the whole folder through four lifecycle directories; the per-item work flows through five verbs. + +## Folder lifecycle + +```text +notes/burnlists/draft// +notes/burnlists/ready// +notes/burnlists/inprogress// +notes/burnlists/completed// +``` + +`draft/` is being created or hardened — do not execute it. `ready/` may be inspected and selected, but is not casually rewritten. Move `ready//` to `inprogress//` before selecting, editing, testing, or deleting any active item. Closeout moves the folder to `completed//`. + +The id is repo-local, short, sortable, and stable, such as `260630-001`; its global identity is the repo root plus id. + +## The five verbs + +| Verb | CLI command | Meaning | +| --- | --- | --- | +| new | `burnlist new` | Create a draft Burnlist. | +| ready | `burnlist ready ` | Mark a plan ready. | +| start | `burnlist start ` | Move ready to inprogress. | +| burn | `burnlist burn ` | Complete one active item. | +| close | `burnlist close ` | Close a completed Burnlist. | + +See the [CLI reference](/cli). + +## Canonical files + +`goal.md` is the stable contract: Goal, Guardrails, Proof Authority, Ordering Intent, Stop Conditions, and Handoff. `burnlist.md` is hot, shrinking task state: top metadata, an ordered `## Active Checklist`, and a terse `## Completed` ledger. `completed.md` is optional durable per-burn history for humans; it is not canonical. + +```markdown +## Active Checklist +- [ ] B1 | + Files/search: `` + Action: + Done/delete when: + Validate: `` + +## Completed +- B0 | | +``` + +An active item is a `- [ ] | ` line with indented `Files/search`, `Action`, `Done/delete when`, and `Validate` fields. Completed work becomes a single terse ledger line under `## Completed`. + +## The burn transaction + +Perform this transaction atomically: + +1. Validate the active item. +2. Generate a local ISO timestamp mechanically with `burnlist --stamp`. +3. Append one `## Completed` ledger line. +4. Delete the active item. +5. Append or update the matching `completed.md` record when useful. +6. Run the protocol check. + +The completed ledger is the only completion source of truth. + +## Protocol check and closeout + +```sh +burnlist --plan notes/burnlists/inprogress/<YYMMDD-NNN>/burnlist.md --check +burnlist --plan notes/burnlists/inprogress/<YYMMDD-NNN>/burnlist.md --digest +burnlist --close-completed --scan-root <repo> +``` + +Fix only reported protocol errors: missing sections, missing or duplicate stable ids, malformed ledger lines, completed ids still active, and checked active items that should move to `## Completed`. A Burnlist is not closed just because it shows 100%: closeout needs 0 active items, a completion digest, a passing check, and the folder moved to `completed/`. + +See the [dashboard](/dashboard) and the [Checklist oven](/ovens/checklist). diff --git a/website/src/content/docs/ovens.mdx b/website/src/content/docs/ovens.mdx new file mode 100644 index 0000000..b0b5f15 --- /dev/null +++ b/website/src/content/docs/ovens.mdx @@ -0,0 +1,88 @@ +--- +title: Ovens +description: The declarative, non-executable recipe format — and how to author one. +--- + +## What an Oven is + +An Oven is a named, declarative recipe for a Burn. It has two canonical files in a lowercase-slug directory: + +```text +<oven-id>/ + instructions.md + detail.json +``` + +`instructions.md` defines the outcome, canonical state, required run inputs, and evidence rules. It must be non-empty and contain a level-one heading, which is the Oven name. `detail.json` is a bounded, versioned, non-executable detail-page skeleton: its grid, controlled widgets and formats, and optional bindings. + +## What an Oven cannot do + +An Oven cannot execute commands or code, collect or transform project data, own or replace canonical project state, mutate Burnlists or other files, import arbitrary UI, or start an agent. It is data, never code. + +## Built-in Ovens + +Five built-in, read-only Ovens ship with Burnlist: + +- Checklist tracks the active work queue and progress. +- Differential Testing provides aligned reference-versus-candidate series, optional aggregate telemetry, and exact-first evidence. +- Streaming Diff surfaces recently published, session-scoped pre-to-post diff cards read from a local feed. +- Performance Tracing renders retained browser-output timing evidence — frame pacing, budget checks, and slow steps — from a project-owned trace run. +- Visual Parity compares trusted reference and candidate frames as isolated render passes, gating each render domain on calibrated channel, mean-delta, and changed-pixel bounds. + +Custom Ovens use the same two-file package and live in ignored `.local/burnlist/ovens/` state, scoped to the repository that owns them. + +## Authoring with the CLI + +```sh +burnlist oven list [--json] +burnlist oven view <id> [--json] [--cell-width <n>] [--cell-height <n>] +burnlist oven create <id> --dir <dir> # dir holds instructions.md + detail.json +burnlist oven create <id> --package <file|-> # JSON: {name?, instructions, detail} +burnlist oven create <id> --instructions <f|-> --detail <f|-> [--name <text>] +burnlist oven update <id> [same inputs as create] +burnlist oven fork <id> <newId> +burnlist oven bind <id> <path> [--repo <path>] # bind an Oven to a normalized data payload +burnlist oven unbind <id> [--repo <path>] +burnlist oven bindings [--repo <path>] # list this repo's Oven data bindings +``` + +`view` prints the detail skeleton as a box-drawing grid and a section table with widget, format, source, cell, and span. Any file input accepts `-` for standard input. `--name` owns the level-one heading. `create` refuses an existing id; use `update`, or `--force`. Built-in Ovens are read-only, so fork one to customize it. + +Validation reuses the same `oven-contract` used by the dashboard. A bad grid — overlap, an out-of-bounds span, an unknown widget or format, or a missing H1 — is rejected before anything is written. This CLI never executes Oven instructions. + +## Grid rules + +The grid has 2–24 columns, 2–32 rows, a `rowHeight` of 32–120, and version `1`. It contains 1–32 sections. Each section id is a unique lowercase slug. `column` and `row` are 1-based; `column + columnSpan - 1 <= columns`, and the same rule applies to rows. Sections may not overlap. A `source` is empty (unbound) or a JSON-pointer-like string beginning with `/`. + +## Widget vocabulary + +| Widget | Intent | +| --- | --- | +| `metric` | One headline value. | +| `progress` | Completion toward a whole — a number in `0..1` (or `0..100` with the `percent` format). | +| `comparison` | Paired reference-versus-candidate series. | +| `status` | A short state label. | +| `timestamp` | A single moment. | +| `line-chart` | A trend over an ordered axis. | +| `bar-chart` | Categories compared with bars. | +| `pie-chart` | Parts of a whole. | +| `chart` | A generic series. | +| `table` | Rows and columns. | +| `list` | Items. | +| `timeline` | Events in time order. | +| `log` | Append-only lines. | +| `markdown` | Prose. | + +## Formats + +The controlled formats applied to a widget value are `plain` (the default), `number`, `percent`, `duration`, and `timestamp`. + +## Binding + +A bound section's `source` is a JSON-pointer into a single read-only data document produced by a project-specific adapter at view time. The adapter is not part of the Oven, and source-value shape is not validated at creation time: the renderer and adapter are the final authority. Record the expected document shape in `instructions.md`, for example in a `## State Contract` section. + +An unbound section is a layout placeholder that renders no data. Rich built-in Ovens instead define a versioned normalized-data contract validated in code. For example, Differential Testing uses `burnlist-differential-testing-data@1`, bound with `burnlist --oven-data differential-testing=<path>`. + +## Runs + +Run Burn records a repository, title, and objective, then copies the selected `instructions.md` and `detail.json` into a new ignored `.local/burnlist/runs/` directory as immutable run provenance. The app does not execute it. diff --git a/website/src/content/docs/ovens/checklist.mdx b/website/src/content/docs/ovens/checklist.mdx new file mode 100644 index 0000000..c67ccc4 --- /dev/null +++ b/website/src/content/docs/ovens/checklist.mdx @@ -0,0 +1,28 @@ +--- +title: Checklist oven +description: The default queue-completion Oven. +--- + +Checklist is the default queue-completion Oven. It observes a shrinking Markdown checklist and answers: how much of the work queue has been burned down? + +## State contract + +Canonical run state is `burnlist.md` with an ordered `## Active Checklist` and a terse `## Completed` ledger. The dashboard observes; it does not replace or silently mutate that state. + +## Direction + +Progress normally moves 0% → 100%. Completed count comes from the completed ledger, remaining from the active checklist, and total equals completed plus remaining. + +## Run inputs + +A run needs a repository, a concise title, and an objective. Planning and execution stay governed by the [Burnlist lifecycle](/lifecycle). + +## Evidence + +Completion is proven by canonical checklist state and the checks each item requires. Dashboard percentages, charts, and logs are reader views, not implementation proof. + +## Detail-page data + +The normalized detail payload may expose `summary`, `active`, `completed`, `timeline`, and `log` fields. Detail-block bindings use JSON-pointer-like source paths and never execute code. + +See [Ovens](/ovens) and the [dashboard](/dashboard). diff --git a/website/src/content/docs/ovens/differential-testing.mdx b/website/src/content/docs/ovens/differential-testing.mdx new file mode 100644 index 0000000..06a24f9 --- /dev/null +++ b/website/src/content/docs/ovens/differential-testing.mdx @@ -0,0 +1,77 @@ +--- +title: Differential Testing oven +description: Generic source-versus-candidate evidence through one normalized contract. +--- + +Differential Testing is the generic source-versus-candidate Oven. It renders aligned machine evidence from any project through one normalized contract, `burnlist-differential-testing-data@1`. It does **not** import project code, execute project commands, select files, repair captures, apply or revert engine edits, or grant authority. The objective is an exact match to a trusted reference; tolerance-state charts are evidence locators, not a substitute for the exact frontier. + +## Operating modes + +`aggregate` uses normalized paired samples and declared tolerances to guide a conventional comparison cycle. `exact-first` uses an optional retained `exactSession` to report the active exact frontier, source-owned producer, composed-loop result, and one next action. + +When `exactSession.strategy` is `exact-first`, exact target selection fails closed: missing, stale, failed, contradictory, or unbound exact evidence produces `blocked`, and the adapter must not fall back to field failure counts, aggregate ticks, Changed, history, or visually prominent intervals. + +## State contract + +Canonical state stays in project-owned captures, reports, retained runtime state, replay/profile data, exact artifacts, checker outputs, and source evidence. A project adapter maps compact facts into `burnlist-differential-testing-data@1`; Burnlist validates and renders only that normalized document. + +The adapter must preserve roles, real sample identity and ordering, source and candidate values, nulls-as-values distinct from numeric zero, missing-as-missing, field semantics, unit, tolerance, and scenario/reference/replay/profile/report/exact identities. It must never invent points, stretch one series to another, combine artifacts from different runs, weaken thresholds, hide regressions, or classify unavailable evidence as a pass. + +Each catalog scenario may also declare an optional `engine` identity (an `id` plus a contained relative `runtimeRoot`) and a `contractSha256`, binding that scenario's evidence to one named runtime engine and one contract revision. + +## Normalized payload + +The required comparison surface contains `summary.runs`, `summary.fields`, and `summary.frames`; a `scenarioCatalog` with one selected scenario; a `refresh` with one event-driven `queued`, `running`, `complete`, or `failed` state; chronological `progress`; reverse-chronological `log`; and aligned `fields` with paired reference/candidate samples and normalized state. + +Optional independent surfaces are `telemetry`, with `authority: "telemetry-only"` and two candidates compared against one reference, and `exactSession`, with `authority: "adapter-attested"` and the one retained exact-first session. When no scenarios exist, publish the explicit empty state: a null selection, an empty catalog, `refresh: null`, zero summaries, and no rows or fields. + +## Trust gate + +The primary comparison is trustworthy only when: + +1. Reference and candidate artifacts are real and named. +2. Scenario, seed, inputs, timing, alignment, and coverage are comparable. +3. Roles, field semantics, units, and tolerances are explicit. +4. Missing values and present nulls are preserved. +5. Summary partitions reconcile with field rows and samples. +6. Incomplete, stale, contradictory, or partially written data is reported as blocked. + +When the gate fails, fix the capture, adapter, or comparison seam before changing runtime behavior. + +## Attestation boundary + +Burnlist validates normalized structure, arithmetic, chronology, required identities, and session consistency, but it cannot prove that a declared digest matches bytes it never receives. `adapter-attested` means the adapter reported those checks, not that Burnlist independently verified project artifacts. + +## Result semantics + +| Exact-first result | Meaning | +| --- | --- | +| `advanced` | No earlier divergence and a strictly later exact prefix; retain the candidate and publish the next session. | +| `complete` | The configured scenario is exact; retain the candidate and enter end-of-scenario work. | +| `rejected` | The candidate has the same or an earlier exact frontier; discard only that candidate and keep the prior retained session. | +| `evidence-only` | Source or tool evidence improved without an engine-retention decision. | +| `blocked` | A concrete source, replay, mapping, evidence, or tool gap prevents a trustworthy decision. | + +| Aggregate result | Meaning | +| --- | --- | +| `pass` | All required trusted comparisons satisfy the declared contract. | +| `improved` | The comparable residual moved toward an exact match. | +| `unchanged` | The comparable residual did not move. | +| `worsened` | The comparable residual moved away from an exact match. | +| `blocked` | Evidence is not trustworthy enough to guide the next action. | + +Threshold loosening, excluded failures, fabricated values, role reversal, and sample truncation never count as improvement. + +## Working with it + +```sh +burnlist differential-testing schema +burnlist differential-testing validate <differential-testing.json> +burnlist differential-testing validate-bundle <bundle/current.json> +burnlist differential-testing sdk +burnlist --oven-data differential-testing=<path> +``` + +Small comparisons may bind the data document directly. Large comparisons use the `burnlist-differential-testing-bundle@1` transport so Burnlist validates field records sequentially and range-reads only the visible page. `burnlist differential-testing sdk` prints the packaged worker module path. + +See [Ovens](/ovens) and the [CLI reference](/cli). diff --git a/website/src/content/docs/ovens/performance-tracing.mdx b/website/src/content/docs/ovens/performance-tracing.mdx new file mode 100644 index 0000000..1e271d9 --- /dev/null +++ b/website/src/content/docs/ovens/performance-tracing.mdx @@ -0,0 +1,31 @@ +--- +title: Performance Tracing oven +description: Retained browser-output timing evidence from a project-owned trace run. +--- + +Performance Tracing renders retained browser-output timing evidence from a project-owned trace run: frame pacing, synchronous step cost, budget checks, renderer trace groups, slow steps, browser identity, and source provenance — without treating instrumentation as gameplay or visual-equivalence authority. + +## State contract + +The project publishes one atomic `performance-tracing-oven@1` JSON report. The report owns capture, deterministic replay, raw Chrome trace retention, samples, machine and browser provenance, and budget evaluation. Burnlist validates and renders that normalized report; it does not execute the trace command or rewrite project evidence. + +## What the report must preserve + +- Canonical prepared route and scenario identity. +- Browser, viewport, machine, and source-file provenance. +- Startup, frame, synchronous-step, trace-group, and residency measurements. +- Bounded per-dispatch phase attribution with source producer and next-probe metadata. +- Ranked frame spikes, residency-changing step spikes, trace hot windows, and top complete events. +- A measured optimization queue whose items name the producer, evidence, next action, and verification metrics. +- Comparable-history context plus the exact command and integrity gate for the next rerun. +- Every declared budget with its actual value and pass/fail result. +- Raw trace and sample artifact bindings. +- Explicit browser-output trust that makes no native-execution or visual-equivalence claim. + +## Blocking rules + +Missing, malformed, partially written, contradictory, or unactionable evidence is blocked. Never guess a producer and never loosen a budget to make a run green. Fix the capture boundary when observer overhead dominates; otherwise change one measured source producer, rerun the retained comparable command, require the same comparison key and zero integrity violations, and keep the change only when the named acceptance metrics improve without structural regressions. + +## Execution boundary + +The Oven is read-only. Project tooling runs traces and atomically publishes the report; Burnlist only validates and displays it. See [Ovens](/ovens) and the [dashboard](/dashboard). diff --git a/website/src/content/docs/ovens/streaming-diff.mdx b/website/src/content/docs/ovens/streaming-diff.mdx new file mode 100644 index 0000000..7a8629b --- /dev/null +++ b/website/src/content/docs/ovens/streaming-diff.mdx @@ -0,0 +1,20 @@ +--- +title: Streaming Diff oven +description: Read-only, session-scoped pre-to-post diff cards from a local feed. +--- + +Streaming Diff is a declarative, read-only Oven for recently published, session-scoped pre-to-post diff cards. Producers write immutable cards to the local feed; the dashboard observes them through the Oven data endpoint. + +## Selecting a feed + +A feed is selected by its logical repository key, worktree key, and session. A feed's activity time indicates recent publication only — it does **not** indicate that an agent or process is live. + +## Boundary + +This package contains no executable renderer, hook, or producer code. The server-side adapter validates and reads the local feed without mutating it. + +## Producing cards + +Producers are wired through Streaming Diff hooks and CLI. `burnlist hooks install --agent codex,claude` merges the Streaming Diff commands into `.codex/hooks.json` and `.claude/settings.json`, preserving existing entries. The low-level surface is `burnlist streaming-diff <ensure-feed|capture|url|hook> ...`. + +See [installation](/install) for hooks and [Ovens](/ovens). diff --git a/website/src/content/docs/ovens/visual-parity.mdx b/website/src/content/docs/ovens/visual-parity.mdx new file mode 100644 index 0000000..6a277fc --- /dev/null +++ b/website/src/content/docs/ovens/visual-parity.mdx @@ -0,0 +1,22 @@ +--- +title: Visual Parity +description: Reference-versus-candidate frame comparison as isolated render passes. +--- + +Visual Parity compares trusted **reference** and **candidate** frames as isolated render passes. It renders machine evidence from a project adapter through one normalized contract, `burnlist-visual-parity-data@1`, and never imports project code, executes commands, selects files, or grants authority. + +## Domains and roles + +Each render domain declares whether it **qualifies** the current scenario (`target`) or remains visible **diagnostic context** (`context`), so unrelated render domains never contaminate one another. A context domain stays visible and keeps its own pass/fail state, but it does not decide the target scenario verdict. + +## Pass criteria + +A passing `target` domain must satisfy its explicit, calibrated bounds: a channel bound, a mean-delta bound, and a changed-pixel bound. The default is zero tolerance — an unqualified difference fails. + +## Honesty rules + +Do not widen a tolerance to make a regression green. Calibrate only a deterministic renderer-boundary residual, with a written rationale, and preserve the zero-tolerance default. Pixel evidence decides visual parity here; gameplay and state authority stay in the linked [Differential Testing](/ovens/differential-testing) payload. + +## State contract + +Canonical state stays in the project's own captures and render output. A project adapter maps that into `burnlist-visual-parity-data@1`; Burnlist validates and renders only that normalized document, and the adapter remains the final authority on pixel evidence. Record the expected document shape in the Oven's `instructions.md`. diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro new file mode 100644 index 0000000..e5fb10f --- /dev/null +++ b/website/src/pages/index.astro @@ -0,0 +1,304 @@ +--- +const skillUrl = new URL('/skill.md', Astro.site).toString(); +--- +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="description" content="A repo-local burndown tracker and read-only observer dashboard for agents." /> + <link rel="icon" href="/favicon.svg" type="image/svg+xml" /> + <title>Burnlist — a repo-local burndown tracker for agents + + + + + +
+ +
+ +
+
+ Zero runtime dependencies · pure Node · MIT +

Burnlist

+

A repo-local burndown tracker for agents.

+

A shrinking Markdown checklist that renders live progress in a read-only observer dashboard. Burnlist owns task state — not your implementation, tests, or delivery — and never executes your work or drives your agents.

+ +
+ +
+

One tracker, five surfaces

+

The same shrinking queue gives agents, repositories, and observers a clear, local view of the work.

+
+

CLI

One command, burnlist, launches the loopback dashboard. Lifecycle verbs (new, ready, start, burn, close) plus protocol helpers (--check, --digest, --stamp) drive the burndown. Run burnlist --help for the full surface.

+

Dashboard

A read-only web observer. It scans the notes/burnlists/ lifecycle folders across your registered repos and refreshes automatically. It never mutates canonical task state.

+

Ovens

Declarative, non-executable recipes for a Burn: two files — instructions.md (outcome + evidence rules) and detail.json (grid, widgets, bindings). Five built-in Ovens ship: Checklist, Differential Testing, Streaming Diff, Performance Tracing, and Visual Parity.

+

Skills

A single agent skill owns the full Burnlist lifecycle — creation, hardening, execution, and maintenance — installable for both Claude and Codex, per-repo or global.

+

Hooks

burnlist hooks install --agent codex,claude merges Streaming Diff commands into .codex/hooks.json and .claude/settings.json, preserving your existing hook entries.

+
+
+ +
+
+

How burnlist works

+
    +
  1. draft/
  2. ready/
  3. inprogress/
  4. completed/
  5. +
+

Ready work moves to inprogress before execution and to completed once the active queue is empty. Each active item is validated before it leaves the checklist: the agent appends a terse completion record, deletes the item, then re-checks the Burnlist. burnlist.md is the canonical shrinking queue; goal.md holds the stable contract.

+

new → ready → start → burn → close

+
+
+ +
+

Install

+

Two ways to get Burnlist running — pick one, or use both.

+
+
+

Install globally with npm

+
+
npm install --global burnlist
+ +
+
+

burnlist launches the dashboard from any project.

+
+
+
+

Give your agent the skill

+
+
{skillUrl}
+ +
+
+

Paste this URL to your coding agent (Claude Code / Codex) — it fetches a complete, self-contained Burnlist skill. No local setup needed.

+
+
+
+

Requires Node.js 18 or newer. Read the getting-started guide →

+
+
+ +
+ +
+ + + + diff --git a/website/src/styles/custom.css b/website/src/styles/custom.css new file mode 100644 index 0000000..89ca420 --- /dev/null +++ b/website/src/styles/custom.css @@ -0,0 +1,141 @@ +/* Burnlist docs — styled to match the dashboard/ovens design system + (black terminal aesthetic, monospace body, Helvetica titles, flat weights). */ + +:root { + /* Body = the dashboard's monospace; titles = the dashboard's Helvetica stack. */ + --sl-font: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', + 'Courier New', monospace; + --sl-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + 'Liberation Mono', monospace; + --title-font: 'Helvetica Neue', Helvetica, ui-sans-serif, system-ui, -apple-system, + BlinkMacSystemFont, 'Segoe UI', sans-serif; + --sl-nav-height: 3.5rem; + --sl-text-code: 0.875rem; + --sl-line-height: 1.7; +} + +/* Dashboard palette (dark is the canonical look). */ +:root, +:root[data-theme='dark'] { + --dash-bg: #000000; + --dash-panel: #111111; + --dash-line: #262626; + --dash-text: #e8e8e8; + --dash-muted: #a8a8a8; + --dash-accent: #5aa2ff; + + --sl-color-accent-low: #13233a; + --sl-color-accent: #5aa2ff; + --sl-color-accent-high: #8bbcff; + --sl-color-white: #e8e8e8; + --sl-color-gray-1: #d0d0d0; + --sl-color-gray-2: #a8a8a8; + --sl-color-gray-3: #6f6f6f; + --sl-color-gray-4: #404040; + --sl-color-gray-5: #262626; + --sl-color-gray-6: #111111; + --sl-color-gray-7: #0a0a0a; + --sl-color-black: #000000; + --sl-color-bg: #000000; + --sl-color-bg-nav: #000000; + --sl-color-bg-sidebar: #000000; + --sl-color-bg-inline-code: rgba(90, 162, 255, 0.12); + --sl-color-hairline-light: #262626; + --sl-color-hairline: #1a1a1a; + --sl-color-text: #e8e8e8; + --sl-color-text-accent: #5aa2ff; +} + +:root[data-theme='light'] { + --dash-bg: #ffffff; + --dash-panel: #f6f6f6; + --dash-line: #e2e2e2; + --dash-text: #171717; + --dash-muted: #5b5b5b; + --dash-accent: #2563eb; + + --sl-color-accent-low: #dbe6ff; + --sl-color-accent: #2563eb; + --sl-color-accent-high: #1d4ed8; + --sl-color-white: #0a0a0a; + --sl-color-gray-1: #262626; + --sl-color-gray-2: #404040; + --sl-color-gray-3: #6f6f6f; + --sl-color-gray-4: #a8a8a8; + --sl-color-gray-5: #e2e2e2; + --sl-color-gray-6: #f6f6f6; + --sl-color-black: #ffffff; + --sl-color-bg: #ffffff; + --sl-color-bg-nav: #ffffff; + --sl-color-bg-sidebar: #fafafa; + --sl-color-bg-inline-code: rgba(37, 99, 235, 0.1); + --sl-color-hairline-light: #e2e2e2; + --sl-color-hairline: #efefef; + --sl-color-text: #171717; + --sl-color-text-accent: #1d4ed8; +} + +/* Flat weights + Helvetica titles = the dashboard's restraint. */ +:where(.sl-markdown-content) :is(h1, h2, h3, h4, h5, h6), +.content-panel h1, +h1, h2 { + font-family: var(--title-font); + font-weight: 500; + letter-spacing: -0.01em; +} +:where(.sl-markdown-content) h1 { font-weight: 500; } + +/* Header: solid black bar with a hairline, matching the dashboard chrome. */ +.page > header.header { + position: fixed; + inset-inline-start: 0; + inset-block-start: 0; + width: 100%; + height: var(--sl-nav-height); + padding: 0; + background: var(--dash-bg); + border-bottom: 1px solid var(--dash-line); + z-index: var(--sl-z-index-navbar); +} + +.main-frame { + padding-top: calc(var(--sl-nav-height) + var(--sl-mobile-toc-height)); +} + +/* Sidebar hairline like the dashboard's panels. */ +.sidebar-pane { + border-inline-end: 1px solid var(--dash-line); +} + +/* Prose. */ +:where(.sl-markdown-content) { + line-height: 1.7; +} +:where(.sl-markdown-content) :is(code, pre) { + font-family: var(--sl-font-mono); +} +:where(.sl-markdown-content) :not(pre) > code { + background: var(--sl-color-bg-inline-code); + border: 1px solid var(--dash-line); + border-radius: 6px; + padding: 0.08em 0.36em; + font-size: 0.9em; +} +:where(.sl-markdown-content) pre { + border: 1px solid var(--dash-line); + border-radius: 8px; +} + +/* Cards / asides get the panel treatment (8px radius, hairline). */ +.card, +.sl-markdown-content .starlight-aside { + border: 1px solid var(--dash-line); + border-radius: 8px; + background: var(--dash-panel); +} + +/* Links carry the accent. */ +:where(.sl-markdown-content) a { + color: var(--sl-color-text-accent); + text-underline-offset: 3px; +} diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 0000000..8bf91d3 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +}