diff --git a/.gitignore b/.gitignore index 0a19790..fe8fc69 100644 --- a/.gitignore +++ b/.gitignore @@ -172,3 +172,13 @@ cython_debug/ # PyPI configuration file .pypirc + +# GR-MCP project-specific +examples/*_patched_*.py +examples/*.wav +tests/scratch/ + +# Astro/Starlight docs site +docs/.astro/ +docs/node_modules/ +docs/dist/ diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..def549b --- /dev/null +++ b/.mcp.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "gnuradio-mcp": { + "type": "stdio", + "command": "uv", + "args": [ + "run", + "--directory", + "/home/rpm/claude/sdr/gr-mcp", + "gnuradio-mcp" + ], + "env": {} + } + } +} \ No newline at end of file diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs new file mode 100644 index 0000000..352738e --- /dev/null +++ b/docs/astro.config.mjs @@ -0,0 +1,48 @@ +import { defineConfig } from 'astro/config'; +import starlight from '@astrojs/starlight'; + +export default defineConfig({ + site: 'https://gr-mcp.supported.systems', + integrations: [ + starlight({ + title: 'GR-MCP', + description: 'GNU Radio MCP Server for programmatic flowgraph control', + social: { + github: 'https://git.supported.systems/MCP/gr-mcp', + }, + editLink: { + baseUrl: 'https://git.supported.systems/MCP/gr-mcp/src/branch/main/docs/', + }, + customCss: ['./src/styles/custom.css'], + sidebar: [ + { + label: 'Getting Started', + autogenerate: { directory: 'getting-started' }, + }, + { + label: 'Guides', + autogenerate: { directory: 'guides' }, + }, + { + label: 'Reference', + items: [ + { label: 'Tools Overview', link: '/reference/tools-overview/' }, + { + label: 'Tool Reference', + collapsed: true, + autogenerate: { directory: 'reference/tools' }, + }, + { label: 'Docker Images', link: '/reference/docker-images/' }, + { label: 'OOT Catalog', link: '/reference/oot-catalog/' }, + ], + }, + { + label: 'Concepts', + autogenerate: { directory: 'concepts' }, + }, + ], + }), + ], + telemetry: false, + devToolbar: { enabled: false }, +}); diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000..aa1c2d2 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,6964 @@ +{ + "name": "gr-mcp-docs", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gr-mcp-docs", + "version": "0.0.1", + "dependencies": { + "@astrojs/starlight": "^0.32.3", + "astro": "^5.2.5", + "sharp": "^0.33.5" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", + "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", + "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", + "license": "MIT" + }, + "node_modules/@astrojs/markdown-remark": { + "version": "6.3.10", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.10.tgz", + "integrity": "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "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", + "shiki": "^3.19.0", + "smol-toml": "^1.5.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.13.tgz", + "integrity": "sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "6.3.10", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.15.0", + "es-module-lexer": "^1.7.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.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.0.tgz", + "integrity": "sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.2", + "stream-replace-string": "^2.0.0", + "zod": "^3.25.76" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.32.6.tgz", + "integrity": "sha512-ASWGwNzq+0TmJ+GJFFxFFxx6Yra7BqIIMQbvOy/cweTHjqejB6mcaEWtS3Mag12LM7tXCES7v/fzmdPgjz8Yxw==", + "license": "MIT", + "dependencies": { + "@astrojs/mdx": "^4.0.5", + "@astrojs/sitemap": "^3.2.1", + "@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.40.0", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "klona": "^2.0.6", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^5.1.5" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", + "integrity": "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.40.2.tgz", + "integrity": "sha512-gXY3v7jbgz6nWKvRpoDxK4AHUPkZRuJsM79vHX/5uhV9/qX6Qnctp/U/dMHog/LCVXcuOps+5nRmf1uxQVPb3w==", + "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.40.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.40.2.tgz", + "integrity": "sha512-aLw5IlDlZWb10Jo/TTDCVsmJhKfZ7FJI83Zo9VDrV0OBlmHAg7klZqw68VDz7FlftIBVAmMby53/MNXPnMjTSQ==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.40.2" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.40.2.tgz", + "integrity": "sha512-t2HMR5BO6GdDW1c1ISBTk66xO503e/Z8ecZdNcr6E4NpUfvY+MRje+LtrcvbBqMwWBBO8RpVKcam/Uy+1GxwKQ==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.40.2", + "shiki": "^1.26.1" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/core": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.29.2.tgz", + "integrity": "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==", + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.29.2", + "@shikijs/engine-oniguruma": "1.29.2", + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/engine-javascript": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz", + "integrity": "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", + "oniguruma-to-es": "^2.2.0" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", + "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/langs": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.29.2.tgz", + "integrity": "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/themes": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.29.2.tgz", + "integrity": "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/types": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", + "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/oniguruma-to-es": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", + "integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==", + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^5.1.1", + "regex-recursion": "^5.1.1" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/regex": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", + "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/regex-recursion": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz", + "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", + "license": "MIT", + "dependencies": { + "regex": "^5.1.1", + "regex-utilities": "^2.3.0" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/shiki": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.29.2.tgz", + "integrity": "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.29.2", + "@shikijs/engine-javascript": "1.29.2", + "@shikijs/engine-oniguruma": "1.29.2", + "@shikijs/langs": "1.29.2", + "@shikijs/themes": "1.29.2", + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.40.2.tgz", + "integrity": "sha512-/XoLjD67K9nfM4TgDlXAExzMJp6ewFKxNpfUw4F7q5Ecy+IU3/9zQQG/O70Zy+RxYTwKGw2MA9kd7yelsxnSmw==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.40.2" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "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.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "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.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "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.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "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.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "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.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "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.0.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.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "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.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "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.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "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.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "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.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.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.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "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.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "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/@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.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", + "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", + "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.4.0.tgz", + "integrity": "sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==", + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", + "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", + "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", + "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", + "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "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.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", + "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", + "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", + "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", + "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", + "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", + "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "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.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/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.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "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": "25.2.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", + "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.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.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "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/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "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": "5.17.1", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.17.1.tgz", + "integrity": "sha512-oD3tlxTaVWGq/Wfbqk6gxzVRz98xa/rYlpe+gU2jXJMSD01k6sEDL01ZlT8mVSYB/rMgnvIOfiQQ3BbLdN237A==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/markdown-remark": "6.3.10", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^4.0.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.1.1", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.6.2", + "diff": "^8.0.3", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.25.0", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.4.0", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.21.0", + "smol-toml": "^1.6.0", + "svgo": "^4.0.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.3", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.4", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.25.1", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.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.40.2", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.40.2.tgz", + "integrity": "sha512-yJMQId0yXSAbW9I6yqvJ3FcjKzJ8zRL7elbJbllkv1ZJPlsI0NI83Pxn1YL1IapEM347EvOOkSW2GL+2+NO61w==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.40.2" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" + } + }, + "node_modules/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/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/astro/node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/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/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "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/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "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": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "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.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "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.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.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.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "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/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", + "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", + "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.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "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/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "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/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, + "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": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "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.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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.40.2", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.40.2.tgz", + "integrity": "sha512-1zIda2rB0qiDZACawzw2rbdBQiWHBT56uBctS+ezFe5XMAaFaHLnnSYND/Kd+dVzO9HfCXRDpzH3d+3fvOWRcw==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.40.2", + "@expressive-code/plugin-frames": "^0.40.2", + "@expressive-code/plugin-shiki": "^0.40.2", + "@expressive-code/plugin-text-markers": "^0.40.2" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "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.2", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.2.tgz", + "integrity": "sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==", + "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-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "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/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/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-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "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": "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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-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.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "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.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "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.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@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.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "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.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "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": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "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.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/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.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", + "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.4.0", + "@pagefind/darwin-x64": "1.4.0", + "@pagefind/freebsd-x64": "1.4.0", + "@pagefind/linux-arm64": "1.4.0", + "@pagefind/linux-x64": "1.4.0", + "@pagefind/windows-x64": "1.4.0" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "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.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "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/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "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.40.2", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.40.2.tgz", + "integrity": "sha512-+kn+AMGCrGzvtH8Q5lC6Y5lnmTV/r33fdmi5QU/IH1KPHKobKr5UnLwJuqHv5jBTSN/0v2wLDS7RTM73FVzqmQ==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.40.2" + } + }, + "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": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.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/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.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shiki": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", + "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.22.0", + "@shikijs/engine-javascript": "3.22.0", + "@shikijs/engine-oniguruma": "3.22.0", + "@shikijs/langs": "3.22.0", + "@shikijs/themes": "3.22.0", + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "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": "8.0.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz", + "integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "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/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "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.4.1" + }, + "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/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "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/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "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.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "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.3", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.3.tgz", + "integrity": "sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA==", + "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.4", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", + "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.5", + "lru-cache": "^11.2.0", + "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": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "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-beta.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/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "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/docs/package.json b/docs/package.json new file mode 100644 index 0000000..b30c7e3 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,18 @@ +{ + "name": "gr-mcp-docs", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro", + "generate-api-docs": "python scripts/generate-api-docs.py" + }, + "dependencies": { + "@astrojs/starlight": "^0.32.3", + "astro": "^5.2.5", + "sharp": "^0.33.5" + } +} diff --git a/docs/scripts/generate-api-docs.py b/docs/scripts/generate-api-docs.py new file mode 100644 index 0000000..de64ee1 --- /dev/null +++ b/docs/scripts/generate-api-docs.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python3 +"""Generate MDX documentation from GR-MCP tool docstrings. + +Usage: + cd docs + python scripts/generate-api-docs.py + +This script introspects the PlatformProvider and RuntimeProvider classes +to extract tool signatures and docstrings, then generates MDX files for +the Starlight documentation site. +""" + +import inspect +import sys +from pathlib import Path +from typing import get_type_hints + +# Add project root to path +PROJECT_ROOT = Path(__file__).parent.parent.parent +sys.path.insert(0, str(PROJECT_ROOT)) + +# Import providers +from gnuradio_mcp.providers.base import PlatformProvider +from gnuradio_mcp.providers.runtime import RuntimeProvider + +OUTPUT_DIR = Path(__file__).parent.parent / "src/content/docs/reference/tools" + +# Tool categorization +TOOL_CATEGORIES = { + "flowgraph": { + "title": "Flowgraph Tools", + "description": "Tools for managing flowgraph structure: blocks, connections, save/load.", + "tools": [ + "get_blocks", + "make_block", + "remove_block", + "save_flowgraph", + "load_flowgraph", + "get_flowgraph_options", + "set_flowgraph_options", + "export_flowgraph_data", + "import_flowgraph_data", + ], + }, + "blocks": { + "title": "Block Tools", + "description": "Tools for block parameter and port management.", + "tools": [ + "get_block_params", + "set_block_params", + "get_block_sources", + "get_block_sinks", + "bypass_block", + "unbypass_block", + ], + }, + "connections": { + "title": "Connection Tools", + "description": "Tools for wiring blocks together.", + "tools": [ + "get_connections", + "connect_blocks", + "disconnect_blocks", + ], + }, + "validation": { + "title": "Validation Tools", + "description": "Tools for checking flowgraph validity.", + "tools": [ + "validate_block", + "validate_flowgraph", + "get_all_errors", + ], + }, + "platform": { + "title": "Platform Tools", + "description": "Tools for discovering available blocks and managing OOT paths.", + "tools": [ + "get_all_available_blocks", + "search_blocks", + "get_block_categories", + "load_oot_blocks", + "add_block_path", + "get_block_paths", + ], + }, + "codegen": { + "title": "Code Generation", + "description": "Tools for generating Python code and evaluating expressions.", + "tools": [ + "generate_code", + "evaluate_expression", + "create_embedded_python_block", + ], + }, + "runtime-mode": { + "title": "Runtime Mode", + "description": "Tools for enabling/disabling runtime features and checking client capabilities.", + "tools": [ + "get_runtime_mode", + "enable_runtime_mode", + "disable_runtime_mode", + "get_client_capabilities", + "list_client_roots", + ], + }, + "docker": { + "title": "Docker Tools", + "description": "Tools for container lifecycle management.", + "tools": [ + "launch_flowgraph", + "list_containers", + "stop_flowgraph", + "remove_flowgraph", + "capture_screenshot", + "get_container_logs", + ], + }, + "xmlrpc": { + "title": "XML-RPC Tools", + "description": "Tools for XML-RPC connection and variable control.", + "tools": [ + "connect", + "connect_to_container", + "disconnect", + "get_status", + "list_variables", + "get_variable", + "set_variable", + "start", + "stop", + "lock", + "unlock", + ], + }, + "controlport": { + "title": "ControlPort Tools", + "description": "Tools for ControlPort/Thrift connection and monitoring.", + "tools": [ + "connect_controlport", + "connect_to_container_controlport", + "disconnect_controlport", + "get_knobs", + "set_knobs", + "get_knob_properties", + "get_performance_counters", + "post_message", + ], + }, + "coverage": { + "title": "Coverage Tools", + "description": "Tools for collecting Python code coverage from containers.", + "tools": [ + "collect_coverage", + "generate_coverage_report", + "combine_coverage", + "delete_coverage", + ], + }, + "oot": { + "title": "OOT Tools", + "description": "Tools for OOT module detection and installation.", + "tools": [ + "detect_oot_modules", + "install_oot_module", + "list_oot_images", + "remove_oot_image", + "build_multi_oot_image", + "list_combo_images", + "remove_combo_image", + ], + }, +} + + +def get_method_info(method): + """Extract signature and docstring from a method.""" + sig = inspect.signature(method) + doc = inspect.getdoc(method) or "No description available." + + # Parse docstring sections + lines = doc.split("\n") + description = [] + args = [] + returns = "" + example = [] + + section = "description" + for line in lines: + stripped = line.strip() + if stripped.startswith("Args:"): + section = "args" + continue + elif stripped.startswith("Returns:"): + section = "returns" + continue + elif stripped.startswith("Example:") or stripped.startswith("Examples:"): + section = "example" + continue + + if section == "description": + description.append(line) + elif section == "args": + args.append(line) + elif section == "returns": + returns += line + "\n" + elif section == "example": + example.append(line) + + # Get parameter info from signature + params = [] + for name, param in sig.parameters.items(): + if name == "self": + continue + param_type = "" + if param.annotation != inspect.Parameter.empty: + param_type = str(param.annotation).replace("typing.", "") + default = "" + if param.default != inspect.Parameter.empty: + default = repr(param.default) + params.append({ + "name": name, + "type": param_type, + "default": default, + }) + + # Get return type + return_type = "" + try: + hints = get_type_hints(method) + if "return" in hints: + return_type = str(hints["return"]).replace("typing.", "") + except Exception: + pass + + return { + "name": method.__name__, + "description": "\n".join(description).strip(), + "params": params, + "returns": returns.strip(), + "return_type": return_type, + "args_doc": "\n".join(args).strip(), + "example": "\n".join(example).strip(), + } + + +def get_all_methods(): + """Get all tool methods from both providers.""" + methods = {} + + # Get PlatformProvider methods + for name, method in inspect.getmembers(PlatformProvider, predicate=inspect.isfunction): + if not name.startswith("_"): + methods[name] = get_method_info(method) + + # Get RuntimeProvider methods + for name, method in inspect.getmembers(RuntimeProvider, predicate=inspect.isfunction): + if not name.startswith("_"): + methods[name] = get_method_info(method) + + return methods + + +def generate_mdx(category_key: str, category: dict, methods: dict) -> str: + """Generate MDX content for a category.""" + lines = [ + "---", + f'title: {category["title"]}', + f'description: {category["description"]}', + "---", + "", + f'{category["description"]}', + "", + ] + + for tool_name in category["tools"]: + if tool_name not in methods: + lines.append(f"## `{tool_name}`") + lines.append("") + lines.append("*Documentation pending.*") + lines.append("") + continue + + info = methods[tool_name] + + lines.append(f"## `{tool_name}`") + lines.append("") + lines.append(info["description"]) + lines.append("") + + # Parameters table + if info["params"]: + lines.append("### Parameters") + lines.append("") + lines.append("| Name | Type | Default | Description |") + lines.append("|------|------|---------|-------------|") + for param in info["params"]: + default = param["default"] if param["default"] else "-" + ptype = param["type"] if param["type"] else "-" + # Extract description from args_doc if available + desc = "-" + if info["args_doc"]: + for arg_line in info["args_doc"].split("\n"): + if arg_line.strip().startswith(f"{param['name']}:"): + desc = arg_line.split(":", 1)[1].strip() + break + lines.append(f"| `{param['name']}` | `{ptype}` | `{default}` | {desc} |") + lines.append("") + + # Returns + if info["returns"] or info["return_type"]: + lines.append("### Returns") + lines.append("") + if info["return_type"]: + lines.append(f"**Type:** `{info['return_type']}`") + lines.append("") + if info["returns"]: + lines.append(info["returns"]) + lines.append("") + + # Example + if info["example"]: + lines.append("### Example") + lines.append("") + lines.append("```python") + lines.append(info["example"]) + lines.append("```") + lines.append("") + + lines.append("---") + lines.append("") + + return "\n".join(lines) + + +def main(): + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + print("Extracting methods from providers...") + methods = get_all_methods() + print(f"Found {len(methods)} methods") + + for category_key, category in TOOL_CATEGORIES.items(): + print(f"Generating {category_key}.mdx...") + content = generate_mdx(category_key, category, methods) + output_path = OUTPUT_DIR / f"{category_key}.mdx" + output_path.write_text(content) + print(f" Wrote {output_path}") + + print("\nDone! Generated MDX files in:") + print(f" {OUTPUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts new file mode 100644 index 0000000..ab4db09 --- /dev/null +++ b/docs/src/content.config.ts @@ -0,0 +1,10 @@ +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/docs/src/content/docs/concepts/architecture.mdx b/docs/src/content/docs/concepts/architecture.mdx new file mode 100644 index 0000000..b881037 --- /dev/null +++ b/docs/src/content/docs/concepts/architecture.mdx @@ -0,0 +1,237 @@ +--- +title: Architecture +description: GR-MCP system architecture and design principles +draft: false +--- + +import { Aside } from '@astrojs/starlight/components'; + +GR-MCP follows a **Middleware + Provider** pattern that abstracts GNU Radio's internal +objects into clean, serializable models suitable for the MCP protocol. + +## High-Level Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ MCP Client │ +│ (Claude, Cursor, etc.) │ +└─────────────────────────────────────────────────────────────────┘ + │ + │ MCP Protocol (stdio/SSE) + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ FastMCP App │ +│ (main.py) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌───────────────┴───────────────┐ + ▼ ▼ +┌─────────────────────────┐ ┌─────────────────────────────────┐ +│ McpPlatformProvider │ │ McpRuntimeProvider │ +│ (29 tools) │ │ (5 always + ~40 dynamic) │ +│ │ │ │ +│ • get_blocks │ │ Always: │ +│ • make_block │ │ • get_runtime_mode │ +│ • connect_blocks │ │ • enable_runtime_mode │ +│ • validate_flowgraph │ │ • disable_runtime_mode │ +│ • generate_code │ │ • get_client_capabilities │ +│ • ... │ │ • list_client_roots │ +│ │ │ │ +│ │ │ Dynamic (when enabled): │ +│ │ │ • launch_flowgraph │ +│ │ │ • connect_to_container │ +│ │ │ • set_variable │ +│ │ │ • ... │ +└─────────────────────────┘ └─────────────────────────────────┘ + │ │ + ▼ ▼ +┌─────────────────────────┐ ┌─────────────────────────────────┐ +│ PlatformProvider │ │ RuntimeProvider │ +│ (Business Logic) │ │ (Business Logic) │ +└─────────────────────────┘ └─────────────────────────────────┘ + │ │ + ▼ ▼ +┌─────────────────────────┐ ┌─────────────────────────────────┐ +│ Middlewares │ │ Middlewares │ +│ │ │ │ +│ • PlatformMiddleware │ │ • DockerMiddleware │ +│ • FlowGraphMiddleware │ │ • XmlRpcMiddleware │ +│ • BlockMiddleware │ │ • ThriftMiddleware │ +│ │ │ • OOTInstallerMiddleware │ +└─────────────────────────┘ └─────────────────────────────────┘ + │ │ + ▼ ▼ +┌─────────────────────────┐ ┌─────────────────────────────────┐ +│ GNU Radio Objects │ │ External Services │ +│ │ │ │ +│ • Platform │ │ • Docker daemon │ +│ • FlowGraph │ │ • XML-RPC servers │ +│ • Block │ │ • Thrift servers │ +│ • Connections │ │ • Container processes │ +└─────────────────────────┘ └─────────────────────────────────┘ +``` + +## Layer Responsibilities + +### MCP Layer (FastMCP) + +- Handles MCP protocol communication +- Registers tools and resources +- Manages the server lifecycle + +### Provider Layer + +Business logic that doesn't know about MCP: + +- **PlatformProvider**: All flowgraph operations (create, edit, validate, save) +- **RuntimeProvider**: All runtime operations (launch, connect, control) + +### Middleware Layer + +Wraps external systems with validation and normalization: + +- **PlatformMiddleware**: Wraps GNU Radio's `Platform` class +- **FlowGraphMiddleware**: Wraps `FlowGraph` with block/connection management +- **BlockMiddleware**: Wraps `Block` with parameter/port access +- **DockerMiddleware**: Wraps Docker SDK operations +- **XmlRpcMiddleware**: Wraps XML-RPC client +- **ThriftMiddleware**: Wraps ControlPort Thrift client +- **OOTInstallerMiddleware**: Manages OOT module builds + +### Models Layer + +Pydantic models for serialization: + +```python +# Examples +class BlockModel(BaseModel): + name: str + key: str + state: str + +class ParamModel(BaseModel): + key: str + value: str + name: str + dtype: str + +class PortModel(BaseModel): + parent: str + key: str + name: str + dtype: str + direction: str +``` + +## Data Flow + +### Flowgraph Operations + +``` +Tool Call: make_block(block_type="osmosdr_source") + │ + ▼ +McpPlatformProvider.make_block() + │ + ▼ +PlatformProvider.make_block(block_name="osmosdr_source") + │ + ▼ +FlowGraphMiddleware.add_block("osmosdr_source") + │ Creates GNU Radio Block object + │ Generates unique name + ▼ +BlockMiddleware(block) + │ + ▼ +Return: "osmosdr_source_0" (str) +``` + +### Runtime Operations + +``` +Tool Call: set_variable(name="freq", value=101.1e6) + │ + ▼ +McpRuntimeProvider._runtime_tools["set_variable"] + │ + ▼ +RuntimeProvider.set_variable(name="freq", value=101.1e6) + │ + ▼ +XmlRpcMiddleware.set_variable("freq", 101.1e6) + │ XML-RPC call to running flowgraph + ▼ +Return: True +``` + +## Dynamic Tool Registration + +Runtime tools are registered dynamically to minimize context usage: + +```python +# At startup: only 5 mode control tools +get_runtime_mode() # Check status +enable_runtime_mode() # Register all runtime tools +disable_runtime_mode() # Unregister runtime tools +get_client_capabilities() # Debug client info +list_client_roots() # Debug client roots + +# After enable_runtime_mode(): ~40 additional tools +launch_flowgraph() +connect_to_container() +set_variable() +# ...etc +``` + + + +## Key Design Decisions + +### Why Middlewares? + +GNU Radio objects have complex internal state and non-serializable references. +Middlewares: +1. Provide a clean interface with Pydantic models +2. Handle validation and error formatting +3. Manage resource lifecycle (connections, processes) + +### Why Providers? + +Providers separate business logic from MCP registration: +1. Testable without MCP infrastructure +2. Reusable in non-MCP contexts +3. Clear dependency injection + +### Why Dynamic Registration? + +Many MCP clients include all tool descriptions in the system prompt. +With 70+ tools, this wastes significant context. Dynamic registration: +1. Starts with minimal tools for flowgraph design +2. Expands when runtime control is needed +3. Can contract back when done + +## File Organization + +``` +src/gnuradio_mcp/ +├── models.py # All Pydantic models +├── utils.py # Helper functions +├── oot_catalog.py # OOT module catalog +├── middlewares/ +│ ├── platform.py # PlatformMiddleware +│ ├── flowgraph.py # FlowGraphMiddleware +│ ├── block.py # BlockMiddleware +│ ├── docker.py # DockerMiddleware +│ ├── xmlrpc.py # XmlRpcMiddleware +│ ├── thrift.py # ThriftMiddleware +│ └── oot.py # OOTInstallerMiddleware +└── providers/ + ├── base.py # PlatformProvider + ├── mcp.py # McpPlatformProvider + ├── runtime.py # RuntimeProvider + └── mcp_runtime.py # McpRuntimeProvider +``` diff --git a/docs/src/content/docs/concepts/dynamic-tools.mdx b/docs/src/content/docs/concepts/dynamic-tools.mdx new file mode 100644 index 0000000..8dd7474 --- /dev/null +++ b/docs/src/content/docs/concepts/dynamic-tools.mdx @@ -0,0 +1,196 @@ +--- +title: Dynamic Tools +description: How GR-MCP dynamically registers runtime tools +draft: false +--- + +import { Aside, Steps } from '@astrojs/starlight/components'; + +GR-MCP uses **dynamic tool registration** to minimize context usage. Runtime tools +are only registered when needed, keeping the tool list small during flowgraph design. + +## The Problem + +Many MCP clients include tool descriptions in the system prompt. With 70+ tools, +this can consume significant context: + +``` +70 tools × ~100 tokens/tool = ~7,000 tokens +``` + +For LLM applications, this is wasteful when you only need flowgraph design tools. + +## The Solution + +GR-MCP splits tools into two groups: + +### Always Available (34 tools) + +Platform tools for flowgraph design: +- `get_blocks`, `make_block`, `remove_block` +- `connect_blocks`, `disconnect_blocks` +- `validate_flowgraph`, `generate_code` +- etc. + +Plus 5 runtime mode control tools: +- `get_runtime_mode` +- `enable_runtime_mode` +- `disable_runtime_mode` +- `get_client_capabilities` +- `list_client_roots` + +### Dynamically Registered (~40 tools) + +Runtime tools loaded via `enable_runtime_mode()`: +- Container lifecycle: `launch_flowgraph`, `stop_flowgraph` +- XML-RPC: `connect`, `set_variable`, `get_variable` +- ControlPort: `connect_controlport`, `get_knobs`, `get_performance_counters` +- Coverage: `collect_coverage`, `generate_coverage_report` +- OOT: `install_oot_module`, `detect_oot_modules` + +## Usage Pattern + + +1. **Flowgraph Design** (29 platform tools + 5 mode tools = 34 total) + + ```python + make_block(block_type="osmosdr_source") + connect_blocks(...) + validate_flowgraph() + generate_code(output_dir="/tmp") + ``` + +2. **Enable Runtime** (adds ~40 tools = ~74 total) + + ```python + enable_runtime_mode() + # Now runtime tools are available + ``` + +3. **Runtime Control** + + ```python + launch_flowgraph(flowgraph_path="/tmp/fm.py") + connect_to_container(name="gr-fm") + set_variable(name="freq", value=101.1e6) + capture_screenshot() + ``` + +4. **Disable Runtime** (back to 34 tools) + + ```python + disable_runtime_mode() + # Runtime tools removed, back to design mode + ``` + + +## Implementation + +### Mode Control Tools + +```python +@self._mcp.tool +def enable_runtime_mode() -> RuntimeModeStatus: + """Enable runtime mode, registering all runtime control tools.""" + if self._runtime_enabled: + return RuntimeModeStatus(enabled=True, ...) + + self._register_runtime_tools() + self._runtime_enabled = True + return RuntimeModeStatus(enabled=True, tools_registered=[...]) +``` + +### Dynamic Registration + +```python +def _register_runtime_tools(self): + """Dynamically register all runtime tools.""" + p = self._provider + + # Connection management + self._add_tool("connect", p.connect) + self._add_tool("disconnect", p.disconnect) + self._add_tool("get_status", p.get_status) + + # Variable control + self._add_tool("list_variables", p.list_variables) + self._add_tool("get_variable", p.get_variable) + self._add_tool("set_variable", p.set_variable) + + # ... more tools ... + + # Docker-dependent tools (only if Docker available) + if p._has_docker: + self._add_tool("launch_flowgraph", p.launch_flowgraph) + # ... + +def _add_tool(self, name: str, func: Callable): + """Add a tool and track it for later removal.""" + self._mcp.add_tool(func) + self._runtime_tools[name] = func +``` + +### Dynamic Unregistration + +```python +def _unregister_runtime_tools(self): + """Remove all dynamically registered runtime tools.""" + for name in list(self._runtime_tools.keys()): + self._mcp.remove_tool(name) + self._runtime_tools.clear() +``` + +## Conditional Registration + +Some tools depend on external services: + +```python +# Docker-dependent tools +if p._has_docker: + self._add_tool("launch_flowgraph", p.launch_flowgraph) + self._add_tool("capture_screenshot", p.capture_screenshot) + + # OOT tools require Docker for building + if p._has_oot: + self._add_tool("install_oot_module", p.install_oot_module) +``` + + + +## Checking Status + +```python +status = get_runtime_mode() +# Returns: RuntimeModeStatus( +# enabled=False, +# tools_registered=[], +# docker_available=True, +# oot_available=True +# ) + +enable_runtime_mode() +status = get_runtime_mode() +# Returns: RuntimeModeStatus( +# enabled=True, +# tools_registered=[ +# "connect", "disconnect", "get_status", +# "list_variables", "get_variable", "set_variable", +# "start", "stop", "lock", "unlock", +# "launch_flowgraph", "list_containers", +# ... +# ], +# docker_available=True, +# oot_available=True +# ) +``` + +## Benefits + +1. **Reduced Context** — ~35 tools instead of ~75 during design +2. **Faster Responses** — Less prompt processing for simple queries +3. **Clear Separation** — Design vs runtime is explicit +4. **Graceful Degradation** — Missing Docker doesn't break design tools +5. **Discovery** — `get_runtime_mode()` shows what's available diff --git a/docs/src/content/docs/concepts/runtime-communication.mdx b/docs/src/content/docs/concepts/runtime-communication.mdx new file mode 100644 index 0000000..6597075 --- /dev/null +++ b/docs/src/content/docs/concepts/runtime-communication.mdx @@ -0,0 +1,265 @@ +--- +title: Runtime Communication +description: How GNU Radio flowgraphs communicate at runtime +draft: false +--- + +import { Aside } from '@astrojs/starlight/components'; + +This document explains how GNU Radio Companion (GRC) communicates with running flowgraph +processes and the two mechanisms available for runtime control. + +## Key Insight: GRC is a Code Generator + +GRC runs flowgraphs as **completely separate subprocesses** via `subprocess.Popen()`. +It does not have built-in runtime control capabilities. + +``` +┌────────────────────┐ subprocess.Popen() ┌─────────────────────┐ +│ GNU Radio │ ────────────────────────► │ Generated Python │ +│ Companion (GRC) │ │ Flowgraph Script │ +│ │ ◄──────────────────────── │ │ +│ (Qt/GTK GUI) │ stdout/stderr pipe │ (gr.top_block) │ +└────────────────────┘ └─────────────────────┘ +``` + +The generated Python script runs independently. To control parameters at runtime, +you must use one of the two communication mechanisms described below. + +## GRC Execution Flow + +``` +.grc file (YAML) + │ + ▼ Platform.load_and_generate_flow_graph() +Generator (Mako templates) + │ + ▼ generator.write() +Python script (with set_*/get_* methods) + │ + ▼ ExecFlowGraphThread -> subprocess.Popen() +Running flowgraph process + │ + ▼ stdout/stderr piped back to GRC console +``` + +### Key GRC Execution Files + +| File | Purpose | +|------|---------| +| `grc/main.py` | Entry point | +| `grc/gui_qt/components/executor.py` | ExecFlowGraphThread subprocess launcher | +| `grc/core/platform.py` | Block registry, flowgraph loading | +| `grc/core/generator/Generator.py` | Generator factory | +| `grc/workflows/common.py` | Base generator classes | +| `grc/workflows/python_nogui/flow_graph_nogui.py.mako` | Mako template for Python | + +## Two Runtime Control Mechanisms + +### 1. XML-RPC Server (Simple, HTTP-based) + +A **block-based approach** — add the `xmlrpc_server` block to your flowgraph to +expose GRC variables over HTTP. + +| Aspect | Details | +|--------|---------| +| Protocol | HTTP (XML-RPC) | +| Default Port | 8080 | +| Setup | Add `XMLRPC Server` block to flowgraph | +| Naming | `set_varname()` / `get_varname()` | +| Type Support | Basic Python types | + +#### How It Works + +1. Add `XMLRPC Server` block to flowgraph +2. GRC variables automatically become `set_X()` / `get_X()` methods +3. Connect with any XML-RPC client (Python, C++, curl, etc.) + +#### Client Example + +```python +import xmlrpc.client + +# Connect to running flowgraph +server = xmlrpc.client.ServerProxy('http://localhost:8080') + +# Read and write variables +print(server.get_freq()) # Read a variable +server.set_freq(145.5e6) # Set a variable + +# Flowgraph control +server.stop() # Stop flowgraph +server.start() # Start flowgraph +server.lock() # Lock flowgraph for modifications +server.unlock() # Unlock flowgraph +``` + +#### Key Files + +| File | Purpose | +|------|---------| +| `gr-blocks/grc/xmlrpc_server.block.yml` | Server block definition | +| `gr-blocks/grc/xmlrpc_client.block.yml` | Client block definition | +| `gr-blocks/examples/xmlrpc/` | Example flowgraphs | + +### 2. ControlPort/Thrift (Advanced, Binary) + +A **configuration-based approach** — blocks register their parameters via `setup_rpc()` +in C++ code. + +| Aspect | Details | +|--------|---------| +| Protocol | Thrift Binary TCP | +| Default Port | 9090 | +| Setup | Enable in config, blocks call `setup_rpc()` | +| Naming | `block_alias::varname` | +| Type Support | Rich (complex, vectors, PMT types) | +| Metadata | Units, min/max, display hints | + +#### Architecture + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Running Flowgraph Process │ +├──────────────────────────────────────────────────────────────────┤ +│ Block A Block B │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ setup_rpc() { │ │ setup_rpc() { │ │ +│ │ add_rpc_var( │ │ add_rpc_var( │ │ +│ │ "gain", │ │ "freq", │ │ +│ │ &get_gain, │ │ &get_freq, │ │ +│ │ &set_gain); │ │ &set_freq); │ │ +│ │ } │ │ } │ │ +│ └────────┬─────────┘ └────────┬─────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌──────────────────────────────────────────────────────────────┐│ +│ │ rpcserver_thrift (port 9090) ││ +│ │ ┌─────────────────┐ ┌─────────────────┐ ││ +│ │ │ setcallbackmap │ │ getcallbackmap │ ││ +│ │ │ "blockA::gain" │ │ "blockA::gain" │ ││ +│ │ │ "blockB::freq" │ │ "blockB::freq" │ ││ +│ │ └─────────────────┘ └─────────────────┘ ││ +│ └──────────────────────────────────────────────────────────────┘│ +└──────────────────────────────────────────────────────────────────┘ + ▲ + │ Thrift Binary Protocol (TCP) + ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ Python Client │ +│ from gnuradio.ctrlport import GNURadioControlPortClient │ +│ │ +│ client = GNURadioControlPortClient(host='localhost', port=9090) │ +│ knobs = client.getKnobs(['blockA::gain', 'blockB::freq']) │ +│ client.setKnobs({'blockA::gain': 2.5}) │ +└──────────────────────────────────────────────────────────────────┘ +``` + +#### Enabling ControlPort + +**~/.gnuradio/config.conf:** + +```ini +[ControlPort] +on = True +edges_list = True + +[thrift] +port = 9090 +nthreads = 2 +``` + +#### Client Example + +```python +from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient + +# Connect to running flowgraph +client = GNURadioControlPortClient(host='localhost', port=9090) + +# Get knobs (read values) +knobs = client.getKnobs(['analog_sig_source_0::frequency']) +print(knobs) + +# Set knobs (write values) +client.setKnobs({'analog_sig_source_0::frequency': 1500.0}) + +# Regex-based retrieval - get all frequency knobs +all_freq_knobs = client.getRe(['.*::frequency']) + +# Get metadata (units, min, max, description) +props = client.properties(['analog_sig_source_0::frequency']) +print(props['analog_sig_source_0::frequency'].units) +print(props['analog_sig_source_0::frequency'].min) +``` + +#### GUI Monitoring Tools + +- **gr-ctrlport-monitor** — Real-time variable inspection +- **gr-perf-monitorx** — Performance profiling visualization + +```bash +gr-ctrlport-monitor localhost 9090 +gr-perf-monitorx localhost 9090 +``` + +#### Key Files + +| File | Purpose | +|------|---------| +| `gnuradio-runtime/lib/controlport/thrift/gnuradio.thrift` | Thrift IDL definition | +| `gnuradio-runtime/include/gnuradio/rpcserver_thrift.h` | Server implementation | +| `gnuradio-runtime/include/gnuradio/rpcregisterhelpers.h` | Registration templates | +| `gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py` | Python client | +| `gnuradio-runtime/python/gnuradio/ctrlport/RPCConnectionThrift.py` | Thrift connection | + +## Comparison: XML-RPC vs ControlPort + +| Feature | XML-RPC | ControlPort/Thrift | +|---------|---------|-------------------| +| Setup | Add block to flowgraph | Enable in config.conf | +| Protocol | HTTP | Binary TCP | +| Performance | Slower (text-based) | Faster (binary) | +| Type support | Basic Python types | Complex, vectors, PMT | +| Metadata | None | Units, min/max, hints | +| Tooling | Any HTTP client | Specialized monitors | +| Use case | Simple control | Performance monitoring | + +### When to Use Each + +**Use XML-RPC when:** +- You need quick, simple remote control +- Integration with web applications +- Language-agnostic client access +- Minimal configuration + +**Use ControlPort when:** +- You need performance monitoring +- Working with complex data types +- Block-level control granularity +- Need metadata about parameters + +## GR-MCP Integration + +GR-MCP wraps both protocols: + +```python +# XML-RPC via GR-MCP +connect_to_container(name="fm-radio") +set_variable(name="freq", value=101.1e6) + +# ControlPort via GR-MCP +connect_to_container_controlport(name="fm-profiled") +get_performance_counters() +``` + + + +## Related Documentation + +- [Runtime Control Guide](/guides/runtime-control/) — XML-RPC usage in GR-MCP +- [ControlPort Monitoring Guide](/guides/controlport/) — ControlPort usage in GR-MCP +- GNU Radio docs: `docs/doxygen/other/ctrlport.dox` — Block implementation guide diff --git a/docs/src/content/docs/getting-started/first-flowgraph.mdx b/docs/src/content/docs/getting-started/first-flowgraph.mdx new file mode 100644 index 0000000..4c08016 --- /dev/null +++ b/docs/src/content/docs/getting-started/first-flowgraph.mdx @@ -0,0 +1,240 @@ +--- +title: Your First Flowgraph +description: Build and validate a complete GNU Radio flowgraph with GR-MCP +draft: false +--- + +import { Steps, Aside, Code, Tabs, TabItem } from '@astrojs/starlight/components'; + +This tutorial walks through building a simple FM receiver flowgraph programmatically. +You'll learn the core workflow: create blocks, set parameters, connect ports, validate, and save. + +## What We're Building + +A basic FM receiver chain: + +``` +osmosdr_source → low_pass_filter → analog_wfm_rcv → audio_sink +``` + +This receives RF at a configurable frequency, filters it, demodulates FM, and outputs audio. + +## Step-by-Step + + +1. **Create the source block** + + ```python + make_block(block_type="osmosdr_source") + # Returns: "osmosdr_source_0" + ``` + + GR-MCP automatically assigns unique names by appending `_0`, `_1`, etc. + +2. **Set source parameters** + + First, inspect available parameters: + + ```python + get_block_params(block_name="osmosdr_source_0") + ``` + + Then configure: + + ```python + set_block_params(block_name="osmosdr_source_0", params={ + "freq": "101.1e6", # 101.1 MHz FM station + "sample_rate": "2e6", # 2 MHz sample rate + "gain": "40", # RF gain + "args": '""' # Auto-detect device + }) + ``` + +3. **Create the filter** + + ```python + make_block(block_type="low_pass_filter") + + set_block_params(block_name="low_pass_filter_0", params={ + "type": "fir_filter_ccf", + "decim": "10", # Decimate by 10 → 200 kHz + "cutoff_freq": "100e3", # 100 kHz cutoff + "transition_width": "10e3", + "win": "window.WIN_HAMMING" + }) + ``` + +4. **Create the FM demodulator** + + ```python + make_block(block_type="analog_wfm_rcv") + + set_block_params(block_name="analog_wfm_rcv_0", params={ + "quad_rate": "200e3", # Input rate after decimation + "audio_decimation": "4" # Output at 50 kHz + }) + ``` + +5. **Create the audio output** + + ```python + make_block(block_type="audio_sink") + + set_block_params(block_name="audio_sink_0", params={ + "samp_rate": "48000", + "device_name": '""' # Default audio device + }) + ``` + +6. **Connect the blocks** + + ```python + connect_blocks( + source_block_name="osmosdr_source_0", + sink_block_name="low_pass_filter_0", + source_port_name="0", + sink_port_name="0" + ) + + connect_blocks( + source_block_name="low_pass_filter_0", + sink_block_name="analog_wfm_rcv_0", + source_port_name="0", + sink_port_name="0" + ) + + connect_blocks( + source_block_name="analog_wfm_rcv_0", + sink_block_name="audio_sink_0", + source_port_name="0", + sink_port_name="0" + ) + ``` + + + +7. **Validate the flowgraph** + + ```python + validate_flowgraph() + # Returns: True + + # Check for any warnings + get_all_errors() + # Returns: [] + ``` + +8. **Save the flowgraph** + + ```python + save_flowgraph(filepath="/tmp/fm_receiver.grc") + ``` + + You can now open this in GNU Radio Companion! + + +## Generate Python Code + +Instead of saving as `.grc`, you can generate executable Python directly: + +```python +result = generate_code(output_dir="/tmp") +# Returns GeneratedCodeModel with: +# - file_path: "/tmp/fm_receiver.py" +# - is_valid: True +# - warnings: [] +``` + + + +## Using Variables + +GR-MCP supports flowgraph variables for runtime tuning. Set them via flowgraph options: + +```python +set_flowgraph_options(params={ + "title": "FM Receiver", + "author": "Your Name", + # Variables are defined here too +}) + +# Or use the expression evaluator to test values +evaluate_expression("101.1e6 + 200e3") # Returns: 101300000.0 +``` + +## Complete Script + +Here's the full example as a Python script: + +```python +#!/usr/bin/env python3 +"""Build an FM receiver with GR-MCP.""" + +import asyncio +from fastmcp import Client + +async def build_fm_receiver(): + # Connect to GR-MCP (running as MCP server) + async with Client("gr-mcp") as client: + # Create blocks + await client.call_tool("make_block", {"block_type": "osmosdr_source"}) + await client.call_tool("make_block", {"block_type": "low_pass_filter"}) + await client.call_tool("make_block", {"block_type": "analog_wfm_rcv"}) + await client.call_tool("make_block", {"block_type": "audio_sink"}) + + # Configure source + await client.call_tool("set_block_params", { + "block_name": "osmosdr_source_0", + "params": { + "freq": "101.1e6", + "sample_rate": "2e6", + "gain": "40" + } + }) + + # Configure filter + await client.call_tool("set_block_params", { + "block_name": "low_pass_filter_0", + "params": { + "type": "fir_filter_ccf", + "decim": "10", + "cutoff_freq": "100e3", + "transition_width": "10e3" + } + }) + + # Connect signal chain + for src, dst in [ + ("osmosdr_source_0", "low_pass_filter_0"), + ("low_pass_filter_0", "analog_wfm_rcv_0"), + ("analog_wfm_rcv_0", "audio_sink_0"), + ]: + await client.call_tool("connect_blocks", { + "source_block_name": src, + "sink_block_name": dst, + "source_port_name": "0", + "sink_port_name": "0" + }) + + # Validate and save + result = await client.call_tool("validate_flowgraph", {}) + print(f"Valid: {result.data}") + + await client.call_tool("save_flowgraph", { + "filepath": "/tmp/fm_receiver.grc" + }) + +if __name__ == "__main__": + asyncio.run(build_fm_receiver()) +``` + +## Next Steps + +- [Running in Docker](/getting-started/running-in-docker/) — Launch the flowgraph with runtime control +- [OOT Modules](/guides/oot-modules/) — Add gr-osmosdr and other modules diff --git a/docs/src/content/docs/getting-started/installation.mdx b/docs/src/content/docs/getting-started/installation.mdx new file mode 100644 index 0000000..edb0ea4 --- /dev/null +++ b/docs/src/content/docs/getting-started/installation.mdx @@ -0,0 +1,173 @@ +--- +title: Installation +description: Set up GR-MCP with UV and GNU Radio +draft: false +--- + +import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components'; + +GR-MCP requires Python 3.14+, GNU Radio with GRC, and optionally Docker for runtime control features. + +## Prerequisites + + + +```bash +# GNU Radio and GRC +sudo pacman -S gnuradio gnuradio-companion + +# UV package manager (if not installed) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Docker (optional, for runtime control) +sudo pacman -S docker docker-compose +sudo systemctl enable --now docker +sudo usermod -aG docker $USER +``` + + +```bash +# GNU Radio and GRC +sudo apt install gnuradio gnuradio-dev + +# UV package manager +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Docker (optional) +sudo apt install docker.io docker-compose +sudo systemctl enable --now docker +sudo usermod -aG docker $USER +``` + + +```bash +# GNU Radio and GRC +sudo dnf install gnuradio gnuradio-devel + +# UV package manager +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Docker (optional) +sudo dnf install docker docker-compose +sudo systemctl enable --now docker +sudo usermod -aG docker $USER +``` + + + +## Install GR-MCP + + +1. **Clone the repository** + + ```bash + git clone https://git.supported.systems/MCP/gr-mcp + cd gr-mcp + ``` + +2. **Create a virtual environment with system site-packages** + + + + ```bash + uv venv --system-site-packages --python 3.14 + ``` + +3. **Install dependencies** + + ```bash + uv sync + ``` + +4. **Verify the installation** + + ```bash + uv run main.py + ``` + + You should see the FastMCP server start. Press `Ctrl+C` to stop. + + +## Configure Your MCP Client + +Add GR-MCP to your MCP client configuration: + + + +Edit `~/.config/claude/claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "gr-mcp": { + "command": "uv", + "args": ["--directory", "/path/to/gr-mcp", "run", "main.py"] + } + } +} +``` + + +```bash +claude mcp add gr-mcp -- uv --directory /path/to/gr-mcp run main.py +``` + +Or add to your project's `.mcp.json`: + +```json +{ + "mcpServers": { + "gr-mcp": { + "command": "uv", + "args": ["--directory", "/path/to/gr-mcp", "run", "main.py"] + } + } +} +``` + + +Edit your Cursor settings or `.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "gr-mcp": { + "command": "uv", + "args": ["--directory", "/path/to/gr-mcp", "run", "main.py"] + } + } +} +``` + + + +## Build Docker Images (Optional) + +For runtime control features (launching flowgraphs in containers, visual feedback, code coverage): + +```bash +cd gr-mcp + +# Base runtime image with Xvfb + VNC + ImageMagick +docker build -f docker/Dockerfile.gnuradio-runtime \ + -t gnuradio-runtime:latest docker/ + +# Coverage image (adds python3-coverage) +docker build -f docker/Dockerfile.gnuradio-coverage \ + -t gnuradio-coverage:latest docker/ +``` + + + +## Next Steps + +Now that GR-MCP is installed, try: + +- [Build your first flowgraph](/getting-started/first-flowgraph/) — Create and validate a complete signal chain +- [Running in Docker](/getting-started/running-in-docker/) — Launch flowgraphs with runtime control diff --git a/docs/src/content/docs/getting-started/running-in-docker.mdx b/docs/src/content/docs/getting-started/running-in-docker.mdx new file mode 100644 index 0000000..a67696a --- /dev/null +++ b/docs/src/content/docs/getting-started/running-in-docker.mdx @@ -0,0 +1,270 @@ +--- +title: Running in Docker +description: Launch flowgraphs in Docker containers with runtime control +draft: false +--- + +import { Steps, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +GR-MCP can run flowgraphs in Docker containers, providing isolation, headless GUI rendering +(via Xvfb), and real-time control through XML-RPC or ControlPort. + +## Prerequisites + +- Docker daemon running (`sudo systemctl start docker`) +- User in the `docker` group (`sudo usermod -aG docker $USER`) +- Base runtime image built (see [Installation](/getting-started/installation/)) + +## Enable Runtime Mode + +Runtime tools are not loaded by default to minimize context usage. Enable them first: + +```python +enable_runtime_mode() +# Returns RuntimeModeStatus with: +# enabled: True +# tools_registered: ["launch_flowgraph", "connect_to_container", ...] +# docker_available: True +# oot_available: True +``` + + + +## Launch a Flowgraph + + +1. **Generate the Python script** + + Docker containers run `.py` files, not `.grc` files. Generate code first: + + ```python + generate_code(output_dir="/tmp") + # Creates /tmp/fm_receiver.py + ``` + +2. **Launch in a container** + + ```python + launch_flowgraph( + flowgraph_path="/tmp/fm_receiver.py", + name="fm-radio", + xmlrpc_port=8080, # Enable XML-RPC on this port + enable_vnc=True # Optional: VNC server on port 5900 + ) + ``` + + Returns a `ContainerModel` with: + - `name`: Container name + - `status`: "running" + - `xmlrpc_port`: Mapped port number + - `vnc_port`: VNC port (if enabled) + +3. **Connect to the running flowgraph** + + ```python + connect_to_container(name="fm-radio") + ``` + + This auto-discovers the XML-RPC port from container labels. + + +## Real-Time Variable Control + +Once connected, you can read and modify flowgraph variables: + +```python +# List available variables +list_variables() +# Returns: [VariableModel(name="freq", value=101100000.0), ...] + +# Read a variable +get_variable(name="freq") +# Returns: 101100000.0 + +# Tune to a different station +set_variable(name="freq", value=98.5e6) +``` + +### Thread-Safe Updates + +For multiple parameter changes, lock the flowgraph first: + +```python +lock() # Pause processing + +set_variable(name="freq", value=102.7e6) +set_variable(name="gain", value=35) + +unlock() # Resume processing +``` + +## Visual Feedback + +### Screenshots + +Capture the QT GUI display: + +```python +screenshot = capture_screenshot(name="fm-radio") +# Returns ScreenshotModel with: +# path: "/tmp/gr-mcp-screenshots/fm-radio-2024-01-15-14-30-00.png" +# width: 1024 +# height: 768 +``` + +### VNC Access + +If launched with `enable_vnc=True`, connect with any VNC client: + +```bash +# Using TigerVNC +vncviewer localhost:5900 + +# Using Remmina +remmina -c vnc://localhost:5900 +``` + +### Container Logs + +Check for errors or debug output: + +```python +get_container_logs(name="fm-radio", tail=50) +# Returns last 50 lines of stdout/stderr +``` + +## Container Lifecycle + +```python +# List all GR-MCP containers +list_containers() + +# Stop a running flowgraph (graceful shutdown) +stop_flowgraph(name="fm-radio") + +# Remove the container +remove_flowgraph(name="fm-radio") + +# Force remove if stuck +remove_flowgraph(name="fm-radio", force=True) +``` + +## Flowgraph Execution Control + +Control the flowgraph's execution state via XML-RPC: + +```python +stop() # Pause execution (flowgraph.stop()) +start() # Resume execution (flowgraph.start()) +``` + +## Using SDR Hardware + +Pass device paths to access hardware inside the container: + +```python +launch_flowgraph( + flowgraph_path="/tmp/fm_receiver.py", + name="fm-hardware", + device_paths=["/dev/bus/usb"] # RTL-SDR access +) +``` + + + +```python +device_paths=["/dev/bus/usb"] +``` + + +```python +device_paths=["/dev/bus/usb"] +``` + + +```python +device_paths=["/dev/bus/usb"] +``` + + +```python +device_paths=["/dev/ttyUSB0"] +``` + + + +## Auto-Image Selection + +For flowgraphs using OOT modules, let GR-MCP auto-detect and build the required image: + +```python +launch_flowgraph( + flowgraph_path="lora_rx.py", + auto_image=True # Detects gr-lora_sdr, builds/uses appropriate image +) +``` + +This: +1. Analyzes the flowgraph for OOT imports +2. Checks if required modules are installed +3. Builds missing modules from the OOT catalog +4. Creates a combo image if multiple OOT modules are needed + +## Complete Example + +```python +#!/usr/bin/env python3 +"""Launch and control an FM receiver via Docker.""" + +import asyncio +import time +from fastmcp import Client + +async def main(): + async with Client("gr-mcp") as client: + # Enable runtime tools + await client.call_tool("enable_runtime_mode", {}) + + # Launch container + result = await client.call_tool("launch_flowgraph", { + "flowgraph_path": "/tmp/fm_receiver.py", + "name": "fm-demo", + "xmlrpc_port": 8080, + "enable_vnc": True + }) + print(f"Container: {result.data.name}") + + # Wait for startup + time.sleep(3) + + # Connect + await client.call_tool("connect_to_container", {"name": "fm-demo"}) + + # Scan FM band + for freq in [88.1e6, 95.5e6, 101.1e6, 107.9e6]: + await client.call_tool("set_variable", { + "name": "freq", + "value": freq + }) + print(f"Tuned to {freq/1e6:.1f} MHz") + time.sleep(2) + + # Capture final state + await client.call_tool("capture_screenshot", {"name": "fm-demo"}) + + # Cleanup + await client.call_tool("stop_flowgraph", {"name": "fm-demo"}) + await client.call_tool("remove_flowgraph", {"name": "fm-demo"}) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## Next Steps + +- [Runtime Control Guide](/guides/runtime-control/) — Advanced XML-RPC patterns +- [ControlPort Monitoring](/guides/controlport/) — Performance metrics via Thrift +- [Code Coverage](/guides/code-coverage/) — Collect coverage from containerized runs diff --git a/docs/src/content/docs/guides/code-coverage.mdx b/docs/src/content/docs/guides/code-coverage.mdx new file mode 100644 index 0000000..fb3ad85 --- /dev/null +++ b/docs/src/content/docs/guides/code-coverage.mdx @@ -0,0 +1,287 @@ +--- +title: Code Coverage +description: Collect Python code coverage from containerized flowgraphs +draft: false +--- + +import { Steps, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +GR-MCP can collect Python code coverage from flowgraphs running in Docker containers. +This is useful for measuring test coverage of GNU Radio applications and embedded Python blocks. + +## How It Works + +1. Launch with `enable_coverage=True` — container runs flowgraph under `coverage.py` +2. Run your test scenarios via XML-RPC variable control +3. Stop the flowgraph gracefully — coverage data is written to disk +4. Collect and analyze — generate reports in HTML, XML, or JSON + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Docker Container │ +│ ┌─────────────────────────────────────────────────────────┐│ +│ │ coverage run flowgraph.py ││ +│ │ └─ writes .coverage.* files on exit ││ +│ └─────────────────────────────────────────────────────────┘│ +│ │ │ +│ ▼ │ +│ /tmp/gr-mcp-coverage/{container_name}/ │ +│ ├─ .coverage │ +│ ├─ .coverage.{hostname}.{pid}.* │ +│ └─ htmlcov/ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Prerequisites + +Build the coverage-enabled Docker image: + +```bash +docker build -f docker/Dockerfile.gnuradio-coverage \ + -t gnuradio-coverage:latest docker/ +``` + + + +## Collect Coverage + + +1. **Launch with coverage enabled** + + ```python + enable_runtime_mode() + + launch_flowgraph( + flowgraph_path="/tmp/flowgraph.py", + name="coverage-test", + enable_coverage=True + ) + ``` + +2. **Run your test scenario** + + ```python + connect_to_container(name="coverage-test") + + # Exercise the flowgraph + set_variable(name="freq", value=100e6) + time.sleep(2) + set_variable(name="freq", value=200e6) + time.sleep(2) + # ... more test actions ... + ``` + +3. **Stop gracefully** (required for coverage data) + + ```python + stop_flowgraph(name="coverage-test") + ``` + + + +4. **Collect the coverage data** + + ```python + collect_coverage(name="coverage-test") + # Returns CoverageDataModel: + # container_name: "coverage-test" + # coverage_file: "/tmp/gr-mcp-coverage/coverage-test/.coverage" + # summary: "Name Stmts Miss Cover\n..." + # lines_covered: 150 + # lines_total: 200 + # coverage_percent: 75.0 + ``` + +5. **Generate a report** + + ```python + generate_coverage_report( + name="coverage-test", + format="html" + ) + # Returns CoverageReportModel: + # report_path: "/tmp/gr-mcp-coverage/coverage-test/htmlcov/index.html" + ``` + + +## Report Formats + + + +```python +generate_coverage_report(name="coverage-test", format="html") +# Creates: /tmp/gr-mcp-coverage/coverage-test/htmlcov/index.html +``` + +Best for visual inspection — shows line-by-line coverage with highlighting. + + +```python +generate_coverage_report(name="coverage-test", format="xml") +# Creates: /tmp/gr-mcp-coverage/coverage-test/coverage.xml +``` + +Cobertura-compatible format for CI/CD integration. + + +```python +generate_coverage_report(name="coverage-test", format="json") +# Creates: /tmp/gr-mcp-coverage/coverage-test/coverage.json +``` + +Machine-readable format for custom analysis. + + + +## Combine Multiple Runs + +Aggregate coverage from multiple test scenarios: + +```python +# Run first scenario +launch_flowgraph(..., name="test-1", enable_coverage=True) +# ... exercise ... +stop_flowgraph(name="test-1") + +# Run second scenario +launch_flowgraph(..., name="test-2", enable_coverage=True) +# ... exercise ... +stop_flowgraph(name="test-2") + +# Combine results +combined = combine_coverage(names=["test-1", "test-2"]) +# Returns CoverageDataModel for combined data + +# Generate combined report +generate_coverage_report(name="combined", format="html") +``` + +## Clean Up Coverage Data + +```python +# Delete specific container's coverage +delete_coverage(name="coverage-test") +# Returns: 1 (directories deleted) + +# Delete old coverage data +delete_coverage(older_than_days=7) +# Returns: number of directories deleted + +# Delete all coverage data +delete_coverage() +# Returns: number of directories deleted +``` + +## Example: Test Suite with Coverage + +```python +#!/usr/bin/env python3 +"""Run a test suite with coverage collection.""" + +import asyncio +import time +from fastmcp import Client + +TEST_SCENARIOS = [ + {"name": "low-freq", "freq": 50e6, "duration": 5}, + {"name": "mid-freq", "freq": 500e6, "duration": 5}, + {"name": "high-freq", "freq": 2.4e9, "duration": 5}, +] + +async def run_test_suite(): + async with Client("gr-mcp") as client: + await client.call_tool("enable_runtime_mode", {}) + + container_names = [] + + for scenario in TEST_SCENARIOS: + name = f"cov-{scenario['name']}" + container_names.append(name) + + print(f"Running scenario: {scenario['name']}") + + # Launch with coverage + await client.call_tool("launch_flowgraph", { + "flowgraph_path": "/tmp/radio_app.py", + "name": name, + "enable_coverage": True + }) + + time.sleep(3) + + # Connect and run scenario + await client.call_tool("connect_to_container", {"name": name}) + await client.call_tool("set_variable", { + "name": "freq", + "value": scenario["freq"] + }) + + time.sleep(scenario["duration"]) + + # Stop gracefully for coverage + await client.call_tool("stop_flowgraph", {"name": name}) + + # Collect this run's coverage + result = await client.call_tool("collect_coverage", {"name": name}) + print(f" Coverage: {result.data.coverage_percent}%") + + # Combine all runs + print("\nCombining coverage from all scenarios...") + combined = await client.call_tool("combine_coverage", { + "names": container_names + }) + print(f"Combined coverage: {combined.data.coverage_percent}%") + + # Generate final report + await client.call_tool("generate_coverage_report", { + "name": "combined", + "format": "html" + }) + print("HTML report: /tmp/gr-mcp-coverage/combined/htmlcov/index.html") + + # Cleanup containers + for name in container_names: + await client.call_tool("remove_flowgraph", {"name": name}) + +if __name__ == "__main__": + asyncio.run(run_test_suite()) +``` + +## Coverage for Embedded Python Blocks + +Coverage includes any embedded Python blocks in your flowgraph: + +```python +# Create an embedded block +source = """ +import numpy as np +from gnuradio import gr + +class my_block(gr.sync_block): + def __init__(self): + gr.sync_block.__init__(self, ...) + + def work(self, input_items, output_items): + # This code path will show in coverage + if input_items[0][0] > 0.5: + output_items[0][:] = input_items[0] * 2 + else: + output_items[0][:] = input_items[0] + return len(output_items[0]) +""" + +create_embedded_python_block(source_code=source) +``` + +Coverage reports will show which branches of your embedded block were exercised. + +## Next Steps + +- [Running in Docker](/getting-started/running-in-docker/) — Container launch basics +- [Runtime Control](/guides/runtime-control/) — Control flowgraphs during tests diff --git a/docs/src/content/docs/guides/controlport.mdx b/docs/src/content/docs/guides/controlport.mdx new file mode 100644 index 0000000..cb8ac06 --- /dev/null +++ b/docs/src/content/docs/guides/controlport.mdx @@ -0,0 +1,262 @@ +--- +title: ControlPort Monitoring +description: Advanced runtime control with performance counters and Thrift +draft: false +--- + +import { Steps, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +ControlPort is GNU Radio's binary protocol for advanced runtime control. It provides +richer functionality than XML-RPC: native type support, performance counters, knob +metadata, and PMT message injection. + +## ControlPort vs XML-RPC + +| Feature | XML-RPC | ControlPort | +|---------|---------|-------------| +| Protocol | HTTP (text) | Thrift (binary) | +| Performance | Slower | Faster | +| Type Support | Basic Python | Complex, vectors, PMT | +| Metadata | None | Units, min/max, hints | +| Perf Counters | No | Yes | +| Message Ports | No | Yes | + +**Use ControlPort when:** +- You need performance profiling (throughput, timing, buffer utilization) +- Working with complex data types (complex numbers, vectors) +- You want parameter metadata (units, valid ranges) +- You need to send PMT messages to blocks + +## Enable ControlPort + +Launch with ControlPort enabled: + +```python +enable_runtime_mode() + +launch_flowgraph( + flowgraph_path="/tmp/fm_receiver.py", + name="fm-profiled", + enable_controlport=True, # Enable ControlPort + controlport_port=9090, # Port (default 9090) + enable_perf_counters=True # Enable performance counters +) +``` + + + +## Connect via ControlPort + + + +```python +connect_to_container_controlport(name="fm-profiled") +``` + + +```python +connect_controlport(host="127.0.0.1", port=9090) +``` + + + +## Working with Knobs + +ControlPort exposes block parameters as "knobs" with the naming pattern: +`block_alias::parameter` + +### Get Knobs + +```python +# Get all knobs +get_knobs(pattern="") +# Returns: [KnobModel(name="osmosdr_source_0::freq_corr", value=0.0, ...), ...] + +# Filter by regex +get_knobs(pattern=".*::freq.*") +# Returns: [KnobModel(name="osmosdr_source_0::freq_corr", ...), ...] + +# Get knobs for a specific block +get_knobs(pattern="osmosdr_source_0::.*") +``` + +### Set Knobs + +```python +# Set multiple knobs atomically +set_knobs({ + "sig_source_0::frequency": 1500.0, + "sig_source_0::amplitude": 0.8 +}) +``` + +### Get Knob Properties + +```python +get_knob_properties(names=["sig_source_0::frequency"]) +# Returns: [KnobPropertiesModel( +# name="sig_source_0::frequency", +# units="Hz", +# min=0.0, +# max=1e9, +# description="Output signal frequency" +# )] +``` + +## Performance Counters + +Monitor block performance in real-time: + +```python +get_performance_counters() +# Returns: [PerfCounterModel( +# block="low_pass_filter_0", +# nproduced=1048576, +# nconsumed=10485760, +# avg_work_time_us=45.3, +# avg_throughput=23.2e6, +# pc_input_buffers_full=0.85, +# pc_output_buffers_full=0.12 +# ), ...] + +# Filter by block +get_performance_counters(block="low_pass_filter_0") +``` + +### Performance Metrics Explained + +| Metric | Description | +|--------|-------------| +| `nproduced` | Items produced by block | +| `nconsumed` | Items consumed by block | +| `avg_work_time_us` | Average time in `work()` function | +| `avg_throughput` | Items per second | +| `pc_input_buffers_full` | Input buffer utilization (0-1) | +| `pc_output_buffers_full` | Output buffer utilization (0-1) | + + + +## PMT Message Injection + +Send messages to block message ports: + +```python +# Send a simple string message +post_message( + block="pdu_sink_0", + port="pdus", + message="hello" +) + +# Send a dict (converted to PMT dict) +post_message( + block="command_handler_0", + port="command", + message={"freq": 1e6, "gain": 40} +) +``` + +## Example: Performance Monitor + +```python +#!/usr/bin/env python3 +"""Monitor flowgraph performance and identify bottlenecks.""" + +import asyncio +import time +from fastmcp import Client + +async def monitor_performance(): + async with Client("gr-mcp") as client: + await client.call_tool("enable_runtime_mode", {}) + + # Launch with ControlPort + await client.call_tool("launch_flowgraph", { + "flowgraph_path": "/tmp/signal_chain.py", + "name": "perf-test", + "enable_controlport": True, + "enable_perf_counters": True + }) + + time.sleep(5) # Let it warm up + + # Connect via ControlPort + await client.call_tool("connect_to_container_controlport", { + "name": "perf-test" + }) + + # Sample performance 10 times + for i in range(10): + result = await client.call_tool("get_performance_counters", {}) + + print(f"\n--- Sample {i+1} ---") + for counter in result.data: + buffer_status = "OK" + if counter.pc_input_buffers_full > 0.9: + buffer_status = "INPUT BOTTLENECK" + elif counter.pc_output_buffers_full > 0.9: + buffer_status = "BLOCK BOTTLENECK" + + print(f"{counter.block}: {counter.avg_throughput/1e6:.2f} MSps, " + f"work={counter.avg_work_time_us:.1f}us [{buffer_status}]") + + time.sleep(1) + + # Cleanup + await client.call_tool("disconnect_controlport", {}) + await client.call_tool("stop_flowgraph", {"name": "perf-test"}) + await client.call_tool("remove_flowgraph", {"name": "perf-test"}) + +if __name__ == "__main__": + asyncio.run(monitor_performance()) +``` + +## Using Both XML-RPC and ControlPort + +You can have both connections active simultaneously: + +```python +# Launch with both +launch_flowgraph( + flowgraph_path="...", + name="dual-control", + xmlrpc_port=8080, + enable_controlport=True +) + +# Connect to XML-RPC for variable control +connect_to_container(name="dual-control") + +# Connect to ControlPort for performance monitoring +connect_to_container_controlport(name="dual-control") + +# Use XML-RPC for simple variable changes +set_variable(name="freq", value=101.1e6) + +# Use ControlPort for performance data +get_performance_counters() + +# Disconnect both +disconnect() # Disconnects both XML-RPC and ControlPort +``` + +## Disconnect ControlPort + +```python +# Disconnect only ControlPort (keep XML-RPC) +disconnect_controlport() + +# Or disconnect everything +disconnect() +``` + +## Next Steps + +- [Runtime Communication Concepts](/concepts/runtime-communication/) — Deep dive into protocol details +- [Code Coverage](/guides/code-coverage/) — Collect test coverage from runtime diff --git a/docs/src/content/docs/guides/oot-modules.mdx b/docs/src/content/docs/guides/oot-modules.mdx new file mode 100644 index 0000000..5edd1ac --- /dev/null +++ b/docs/src/content/docs/guides/oot-modules.mdx @@ -0,0 +1,251 @@ +--- +title: Working with OOT Modules +description: Install and manage GNU Radio Out-of-Tree modules +draft: false +--- + +import { Steps, Aside, Tabs, TabItem, CardGrid, Card } from '@astrojs/starlight/components'; + +Out-of-Tree (OOT) modules extend GNU Radio with specialized functionality like LoRa demodulation, +ADS-B decoding, or hardware support. GR-MCP provides tools to discover, install, and combine +OOT modules into Docker images. + +## Browse Available Modules + +GR-MCP includes a curated catalog of 24 OOT modules accessible via MCP resources: + +```python +# List all modules +# Resource: oot://directory + +# Get details for a specific module +# Resource: oot://directory/lora_sdr +``` + + + + Available in the base `gnuradio-runtime` image: osmosdr, satellites, gsm, rds, fosphor, air_modes, etc. + + + Built on-demand: lora_sdr, ieee802_11, adsb, iridium, dab, nrsc5, etc. + + + +### Module Categories + +| Category | Modules | +|----------|---------| +| **Hardware** | osmosdr, funcube, hpsdr, limesdr | +| **Satellite** | satellites, satnogs, iridium, leo | +| **Cellular** | gsm | +| **Broadcast** | rds, dab, dl5eu, nrsc5 | +| **Aviation** | air_modes, adsb | +| **IoT** | lora_sdr, ieee802_15_4 | +| **WiFi** | ieee802_11 | +| **Analysis** | iqbal, radar, inspector | +| **Visualization** | fosphor | +| **Utility** | foo | + +## Install a Module + + +1. **Enable runtime mode** (required for Docker operations) + + ```python + enable_runtime_mode() + ``` + +2. **Install the module** + + For catalog modules: + + ```python + install_oot_module( + git_url="https://github.com/tapparelj/gr-lora_sdr", + branch="master" + ) + ``` + + This: + - Clones the repository + - Compiles with cmake + - Creates a Docker image: `gnuradio-lora_sdr-runtime:latest` + +3. **Use the new image** + + ```python + launch_flowgraph( + flowgraph_path="lora_rx.py", + image="gnuradio-lora_sdr-runtime:latest" + ) + ``` + + +### With Build Dependencies + +Some modules need extra packages for compilation: + +```python +install_oot_module( + git_url="https://github.com/hboeglen/gr-dab", + branch="maint-3.10", + build_deps=["autoconf", "automake", "libtool", "libfaad-dev"], + cmake_args=["-DENABLE_DOXYGEN=OFF"] +) +``` + + + +## Detect Required Modules + +GR-MCP can analyze flowgraphs to identify OOT dependencies: + +```python +detect_oot_modules(flowgraph_path="lora_rx.py") +# Returns OOTDetectionResult: +# detected_modules: ["lora_sdr"] +# unknown_blocks: [] +# recommended_image: "gnuradio-lora_sdr-runtime:latest" +``` + +For `.grc` files, detection uses prefix matching against the catalog. +For `.py` files, it parses Python imports for more accuracy. + +## Auto-Image Selection + +The simplest approach: let GR-MCP handle everything: + +```python +launch_flowgraph( + flowgraph_path="lora_rx.py", + auto_image=True +) +``` + +This automatically: +1. Detects required OOT modules +2. Installs missing modules from the catalog +3. Builds combo images if multiple modules are needed +4. Launches with the appropriate image + +## Combine Multiple Modules + +Some flowgraphs need multiple OOT modules. Create a combo image: + +```python +build_multi_oot_image(module_names=["lora_sdr", "adsb"]) +# Returns ComboImageResult: +# success: True +# image: ComboImageInfo( +# combo_key: "combo:adsb+lora_sdr", +# image_tag: "gr-combo-adsb-lora_sdr:latest", +# modules: ["adsb", "lora_sdr"] +# ) +``` + +Missing modules are auto-built from the catalog. + +### Launch with Combo Image + +```python +launch_flowgraph( + flowgraph_path="multi_protocol_rx.py", + image="gr-combo-adsb-lora_sdr:latest" +) +``` + +Or use auto-detection: + +```python +detect_oot_modules("multi_protocol_rx.py") +# detected_modules: ["lora_sdr", "adsb"] +# recommended_image: "gr-combo-adsb-lora_sdr:latest" +``` + +## Manage Installed Images + +```python +# List installed OOT images +list_oot_images() +# Returns: [OOTImageInfo(module_name="lora_sdr", image_tag="..."), ...] + +# List combo images +list_combo_images() +# Returns: [ComboImageInfo(combo_key="combo:adsb+lora_sdr", ...)] + +# Remove a single-module image +remove_oot_image(module_name="lora_sdr") + +# Remove a combo image +remove_combo_image(combo_key="combo:adsb+lora_sdr") +``` + +## Load OOT Blocks at Design Time + +For flowgraph design (without Docker), load OOT block definitions: + +```python +# Add a path containing .block.yml files +add_block_path("/usr/local/share/gnuradio/grc/blocks") + +# Check current paths and block count +get_block_paths() +# Returns BlockPathsModel with paths and total_blocks + +# Load multiple paths at once +load_oot_blocks(paths=[ + "/usr/local/share/gnuradio/grc/blocks", + "/home/user/gr-modules/lib/grc" +]) +``` + + + +## Example: LoRa Receiver Setup + +Complete workflow for receiving LoRa packets: + +```python +# 1. Enable runtime mode +enable_runtime_mode() + +# 2. Check if lora_sdr is available +# Resource: oot://directory/lora_sdr +# -> preinstalled: False, installed: False + +# 3. Install the module +install_oot_module( + git_url="https://github.com/tapparelj/gr-lora_sdr", + branch="master" +) +# -> image_tag: "gnuradio-lora_sdr-runtime:latest" + +# 4. Load blocks for design +add_block_path("/usr/local/share/gnuradio/grc/blocks") + +# 5. Build the flowgraph +make_block(block_type="lora_sdr_lora_sdr_lora_rx") +# ... configure and connect ... + +# 6. Generate code +generate_code(output_dir="/tmp") + +# 7. Launch with the new image +launch_flowgraph( + flowgraph_path="/tmp/lora_rx.py", + name="lora-receiver", + image="gnuradio-lora_sdr-runtime:latest", + device_paths=["/dev/bus/usb"] +) +``` + +## Next Steps + +- [Runtime Control](/guides/runtime-control/) — Control variables while the flowgraph runs +- [OOT Catalog Reference](/reference/oot-catalog/) — Full module list with install examples diff --git a/docs/src/content/docs/guides/runtime-control.mdx b/docs/src/content/docs/guides/runtime-control.mdx new file mode 100644 index 0000000..de4e4c8 --- /dev/null +++ b/docs/src/content/docs/guides/runtime-control.mdx @@ -0,0 +1,207 @@ +--- +title: Runtime Control +description: Control flowgraph variables and execution in real-time +draft: false +--- + +import { Steps, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +Once a flowgraph is running, you can read and modify variables, control execution, +and monitor status through XML-RPC. This enables real-time tuning without restarting. + +## How It Works + +Generated flowgraphs expose variables through an XML-RPC server: + +``` +┌─────────────────────────────┐ XML-RPC (HTTP) ┌─────────────────┐ +│ GR-MCP │ ←─────────────────────→ │ Flowgraph │ +│ (MCP Server) │ get_freq() │ (Python) │ +│ │ set_freq(101.1e6) │ │ +└─────────────────────────────┘ └─────────────────┘ +``` + +The flowgraph's variables become `get_X()` and `set_X()` methods automatically. + +## Prerequisites + +Your flowgraph must include an XML-RPC server block, or be launched via GR-MCP's +`launch_flowgraph()` which configures this automatically. + + + +## Connect to a Flowgraph + + + +```python +# Most common: connect to a GR-MCP container +connect_to_container(name="fm-radio") +``` + + +```python +# Direct connection to any XML-RPC endpoint +connect(url="http://localhost:8080") +``` + + + +Both methods return `ConnectionInfoModel` with endpoint details. + +## Variable Operations + +### List Variables + +```python +list_variables() +# Returns: [ +# VariableModel(name="freq", value=101100000.0, type="float"), +# VariableModel(name="gain", value=40, type="int"), +# VariableModel(name="samp_rate", value=2000000.0, type="float"), +# ] +``` + +### Get a Variable + +```python +get_variable(name="freq") +# Returns: 101100000.0 +``` + +### Set a Variable + +```python +set_variable(name="freq", value=98.5e6) +# Returns: True +``` + + + +## Thread-Safe Updates + +For multiple parameter changes: + +```python +# Pause signal processing +lock() + +# Make changes (no audio during this) +set_variable(name="freq", value=102.7e6) +set_variable(name="gain", value=35) +set_variable(name="bandwidth", value=200e3) + +# Resume processing +unlock() +``` + +The flowgraph buffers input during the lock and processes it on unlock. + +## Execution Control + +```python +# Stop the flowgraph (tb.stop()) +stop() + +# Restart the flowgraph (tb.start()) +start() +``` + + + +## Check Connection Status + +```python +get_status() +# Returns RuntimeStatusModel: +# connected: True +# connection: ConnectionInfoModel(url="http://localhost:8080", ...) +# containers: [ContainerModel(...), ...] +``` + +## Disconnect + +```python +disconnect() +# Closes XML-RPC connection (and ControlPort if active) +``` + +## Example: FM Band Scanner + +```python +#!/usr/bin/env python3 +"""Scan FM broadcast band and measure signal strength.""" + +import asyncio +import time +from fastmcp import Client + +FM_STATIONS = [88.1, 90.3, 94.7, 98.5, 101.1, 103.5, 107.9] + +async def scan_fm_band(): + async with Client("gr-mcp") as client: + await client.call_tool("enable_runtime_mode", {}) + + # Launch with a signal strength indicator + await client.call_tool("launch_flowgraph", { + "flowgraph_path": "/tmp/fm_scanner.py", + "name": "fm-scanner", + "xmlrpc_port": 8080 + }) + + time.sleep(3) + await client.call_tool("connect_to_container", {"name": "fm-scanner"}) + + # Scan each station + results = [] + for freq_mhz in FM_STATIONS: + await client.call_tool("set_variable", { + "name": "freq", + "value": freq_mhz * 1e6 + }) + time.sleep(0.5) # Allow settling + + # Read signal level (if flowgraph exposes it) + try: + level = await client.call_tool("get_variable", {"name": "signal_level"}) + results.append((freq_mhz, level.data)) + except Exception: + results.append((freq_mhz, None)) + + # Report findings + for freq, level in results: + status = f"{level:.1f} dB" if level else "no signal" + print(f"{freq:.1f} MHz: {status}") + + # Cleanup + await client.call_tool("stop_flowgraph", {"name": "fm-scanner"}) + await client.call_tool("remove_flowgraph", {"name": "fm-scanner"}) + +if __name__ == "__main__": + asyncio.run(scan_fm_band()) +``` + +## Error Handling + +Common errors and solutions: + +| Error | Cause | Solution | +|-------|-------|----------| +| "Not connected" | No active XML-RPC connection | Call `connect()` or `connect_to_container()` | +| "Unknown variable" | Variable not exposed via XML-RPC | Check flowgraph has XML-RPC block with callback vars | +| "Connection refused" | Container not running / wrong port | Verify with `list_containers()`, `get_status()` | +| "Timeout" | Flowgraph unresponsive | Check logs with `get_container_logs()` | + +## Next Steps + +- [ControlPort Monitoring](/guides/controlport/) — Advanced monitoring with performance counters +- [Code Coverage](/guides/code-coverage/) — Collect coverage from runtime tests diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx new file mode 100644 index 0000000..902d14e --- /dev/null +++ b/docs/src/content/docs/index.mdx @@ -0,0 +1,116 @@ +--- +title: GR-MCP +description: GNU Radio MCP Server for programmatic, automated, and AI-driven flowgraph control +draft: false +template: splash +hero: + tagline: Build, run, and control GNU Radio flowgraphs through the Model Context Protocol + actions: + - text: Get Started + link: /getting-started/installation/ + icon: right-arrow + - text: View on Git + link: https://git.supported.systems/MCP/gr-mcp + icon: external + variant: minimal +--- + +import { Card, CardGrid, Tabs, TabItem } from '@astrojs/starlight/components'; + +## What is GR-MCP? + +GR-MCP is a [FastMCP](https://gofastmcp.com) server that exposes GNU Radio's flowgraph capabilities through the [Model Context Protocol](https://modelcontextprotocol.io). This enables: + +- **Programmatic flowgraph creation** — Build `.grc` files from code or automation +- **Runtime control** — Adjust variables and monitor performance without restarting +- **Docker isolation** — Run flowgraphs in containers with automatic VNC/X11 support +- **OOT module management** — Install and combine Out-of-Tree modules on demand + + + + Create blocks, connect ports, validate flowgraphs, and generate Python code + + + Launch containers, control XML-RPC variables, monitor ControlPort knobs + + + Curated catalog with gr-osmosdr, gr-satellites, gr-lora_sdr, and more + + + Runtime tools load on-demand to minimize context usage + + + +## Quick Example + + + +```python +# Create an FM receiver flowgraph +make_block(block_type="osmosdr_source") +make_block(block_type="low_pass_filter") +make_block(block_type="analog_wfm_rcv") +make_block(block_type="audio_sink") + +# Connect the signal chain +connect_blocks("osmosdr_source_0", "0", "low_pass_filter_0", "0") +connect_blocks("low_pass_filter_0", "0", "analog_wfm_rcv_0", "0") +connect_blocks("analog_wfm_rcv_0", "0", "audio_sink_0", "0") + +# Validate and save +validate_flowgraph() +save_flowgraph("/tmp/fm_receiver.grc") +``` + + +```python +# Launch flowgraph in Docker container +launch_flowgraph( + flowgraph_path="/tmp/fm_receiver.py", + name="fm-radio", + xmlrpc_port=8080, + enable_vnc=True +) + +# Connect and tune +connect_to_container("fm-radio") +set_variable("freq", 101.1e6) + +# Capture display +capture_screenshot("fm-radio") +``` + + +```python +# Check what's available +# Resource: oot://directory + +# Install gr-lora_sdr for LoRa reception +install_oot_module( + git_url="https://github.com/tapparelj/gr-lora_sdr", + branch="master" +) + +# Launch with the new module +launch_flowgraph( + flowgraph_path="lora_rx.py", + image="gnuradio-lora_sdr-runtime:latest" +) +``` + + + +## Getting Started + + + + Set up GR-MCP with `uv` and GNU Radio + + [Read the guide →](/getting-started/installation/) + + + Build and validate a complete flowgraph + + [Follow the tutorial →](/getting-started/first-flowgraph/) + + diff --git a/docs/src/content/docs/reference/docker-images.mdx b/docs/src/content/docs/reference/docker-images.mdx new file mode 100644 index 0000000..dc1172f --- /dev/null +++ b/docs/src/content/docs/reference/docker-images.mdx @@ -0,0 +1,204 @@ +--- +title: Docker Images +description: Reference for GR-MCP Docker images +draft: false +--- + +import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +GR-MCP uses Docker images for running flowgraphs in isolation with headless GUI support. + +## Base Images + +### `gnuradio-runtime:latest` + +The base runtime image for running flowgraphs. + +**Includes:** +- GNU Radio 3.10.x +- Xvfb (virtual framebuffer for headless X11) +- VNC server (optional visual debugging) +- ImageMagick (screenshot capture) +- 12 preinstalled OOT modules (osmosdr, satellites, gsm, etc.) + +**Build:** +```bash +docker build -f docker/Dockerfile.gnuradio-runtime \ + -t gnuradio-runtime:latest docker/ +``` + +### `gnuradio-coverage:latest` + +Extended runtime image with Python coverage support. + +**Includes:** +- Everything in `gnuradio-runtime` +- `python3-coverage` package + +**Build:** +```bash +docker build -f docker/Dockerfile.gnuradio-coverage \ + -t gnuradio-coverage:latest docker/ +``` + +## OOT Module Images + +When you install an OOT module via `install_oot_module()`, GR-MCP creates a new image. + +### Naming Convention + +``` +gnuradio-{module_name}-runtime:latest +``` + +**Examples:** +- `gnuradio-lora_sdr-runtime:latest` +- `gnuradio-adsb-runtime:latest` +- `gnuradio-ieee802_11-runtime:latest` + +### Build Process + +1. Clones the git repository +2. Builds with cmake in a multi-stage Docker build +3. Installs into the runtime image +4. Tags with the module name + +## Combo Images + +For flowgraphs requiring multiple OOT modules, GR-MCP creates combo images. + +### Naming Convention + +``` +gr-combo-{module1}-{module2}-...:latest +``` + +Modules are sorted alphabetically in the name. + +**Examples:** +- `gr-combo-adsb-lora_sdr:latest` +- `gr-combo-adsb-ieee802_11-lora_sdr:latest` + +### Combo Keys + +Combo images are tracked by a `combo_key`: + +``` +combo:{module1}+{module2}+... +``` + +**Examples:** +- `combo:adsb+lora_sdr` +- `combo:adsb+ieee802_11+lora_sdr` + +## Container Configuration + +When launching a flowgraph, containers are configured with: + +| Feature | Configuration | +|---------|---------------| +| Display | `DISPLAY=:99` (Xvfb) | +| X11 | Xvfb runs on `:99` | +| VNC | Port 5900 (if enabled) | +| XML-RPC | Dynamic port mapping | +| ControlPort | Port 9090 (if enabled) | +| Workdir | `/flowgraph` | +| Network | Host network mode | + +### Labels + +GR-MCP containers are tagged with labels for management: + +``` +gr-mcp=true +gr-mcp-name={container_name} +gr-mcp-xmlrpc-port={port} +gr-mcp-vnc={enabled} +gr-mcp-controlport={enabled} +gr-mcp-controlport-port={port} +gr-mcp-coverage={enabled} +``` + +### Volume Mounts + +| Host Path | Container Path | Purpose | +|-----------|----------------|---------| +| Flowgraph file | `/flowgraph/{name}.py` | Flowgraph script | +| Coverage dir | `/coverage` | Coverage data output | + +## Hardware Access + +To access SDR hardware inside containers, pass device paths: + + + +```python +launch_flowgraph( + flowgraph_path="...", + device_paths=["/dev/bus/usb"] +) +``` +Grants access to all USB devices (RTL-SDR, HackRF, Airspy, etc.) + + +```python +launch_flowgraph( + flowgraph_path="...", + device_paths=["/dev/ttyUSB0"] +) +``` +Grants access to specific serial ports (USRP, etc.) + + +```python +launch_flowgraph( + flowgraph_path="...", + device_paths=["/dev/bus/usb", "/dev/ttyUSB0"] +) +``` + + + + + +## Image Management + +### List Images + +```python +# List OOT images +list_oot_images() + +# List combo images +list_combo_images() +``` + +### Remove Images + +```python +# Remove single-module image +remove_oot_image(module_name="lora_sdr") + +# Remove combo image +remove_combo_image(combo_key="combo:adsb+lora_sdr") +``` + +### Docker Commands + +```bash +# List all GR-MCP images +docker images | grep -E "gnuradio-|gr-combo" + +# Remove all GR-MCP images +docker images --format '{{.Repository}}:{{.Tag}}' | \ + grep -E "gnuradio-|gr-combo" | xargs docker rmi + +# List all GR-MCP containers +docker ps -a --filter "label=gr-mcp=true" + +# Remove all stopped GR-MCP containers +docker container prune --filter "label=gr-mcp=true" +``` diff --git a/docs/src/content/docs/reference/oot-catalog.mdx b/docs/src/content/docs/reference/oot-catalog.mdx new file mode 100644 index 0000000..7a2ae50 --- /dev/null +++ b/docs/src/content/docs/reference/oot-catalog.mdx @@ -0,0 +1,249 @@ +--- +title: OOT Catalog +description: Curated catalog of GNU Radio Out-of-Tree modules +draft: false +--- + +import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; + +GR-MCP includes a curated catalog of 24 OOT modules. 12 are preinstalled in the base +runtime image; 12 can be installed on-demand. + +## Accessing the Catalog + +The catalog is exposed as MCP resources: + +```python +# List all modules +# Resource: oot://directory + +# Get details for a specific module +# Resource: oot://directory/lora_sdr +``` + +## Preinstalled Modules + +These modules are included in `gnuradio-runtime:latest`: + +### Hardware + +| Module | Description | +|--------|-------------| +| **osmosdr** | Hardware source/sink for RTL-SDR, Airspy, HackRF, and more | +| **funcube** | Funcube Dongle Pro/Pro+ controller and source block | +| **hpsdr** | OpenHPSDR Protocol 1 interface for HPSDR hardware | +| **limesdr** | LimeSDR source/sink blocks (LMS7002M) | + +### Satellite + +| Module | Description | +|--------|-------------| +| **satellites** | Satellite telemetry decoders (AX.25, CCSDS, AO-73, etc.) | +| **satnogs** | SatNOGS satellite ground station decoders and deframers | + +### Broadcast + +| Module | Description | +|--------|-------------| +| **rds** | FM RDS/RBDS (Radio Data System) decoder | + +### Cellular + +| Module | Description | +|--------|-------------| +| **gsm** | GSM/GPRS burst receiver and channel decoder | + +### Aviation + +| Module | Description | +|--------|-------------| +| **air_modes** | Mode-S/ADS-B aircraft transponder decoder (1090 MHz) | + +### Analysis & Visualization + +| Module | Description | +|--------|-------------| +| **iqbal** | Blind IQ imbalance estimator and correction | +| **radar** | Radar signal processing toolbox (FMCW, OFDM radar) | +| **fosphor** | GPU-accelerated real-time spectrum display (OpenCL) | + +## Installable Modules + +Install these with `install_oot_module()`: + +### IoT + +#### gr-lora_sdr + +LoRa PHY transceiver (CSS modulation/demodulation) + +```python +install_oot_module( + git_url="https://github.com/tapparelj/gr-lora_sdr", + branch="master" +) +``` + +#### gr-ieee802_15_4 + +IEEE 802.15.4 (Zigbee) O-QPSK transceiver + +```python +install_oot_module( + git_url="https://github.com/bastibl/gr-ieee802-15-4", + branch="maint-3.10", + build_deps=["castxml"] +) +``` + +### WiFi + +#### gr-ieee802_11 + +IEEE 802.11a/g/p OFDM transceiver + +```python +install_oot_module( + git_url="https://github.com/bastibl/gr-ieee802-11", + branch="maint-3.10", + build_deps=["castxml"] +) +``` + +### Aviation + +#### gr-adsb + +ADS-B (1090 MHz) aircraft transponder decoder + +```python +install_oot_module( + git_url="https://github.com/mhostetter/gr-adsb", + branch="maint-3.10" +) +``` + +### Satellite + +#### gr-iridium + +Iridium satellite burst detector and demodulator + +```python +install_oot_module( + git_url="https://github.com/muccc/gr-iridium", + branch="master" +) +``` + +#### gr-leo + +LEO satellite channel simulator (Doppler, path loss, atmosphere) + +```python +install_oot_module( + git_url="https://gitlab.com/librespacefoundation/gr-leo", + branch="gnuradio-3.10" +) +``` + +### Broadcast + +#### gr-dab + +DAB/DAB+ digital audio broadcast receiver + +```python +install_oot_module( + git_url="https://github.com/hboeglen/gr-dab", + branch="maint-3.10", + build_deps=["autoconf", "automake", "libtool", "libfaad-dev"], + cmake_args=["-DENABLE_DOXYGEN=OFF"] +) +``` + +#### gr-nrsc5 + +HD Radio (NRSC-5) digital broadcast decoder + +```python +install_oot_module( + git_url="https://github.com/argilo/gr-nrsc5", + branch="master", + build_deps=["autoconf", "automake", "libtool"] +) +``` + +#### gr-dl5eu + +DVB-T OFDM synchronization and TPS decoder + +```python +install_oot_module( + git_url="https://github.com/dl5eu/gr-dl5eu", + branch="main" +) +``` + +### Utility + +#### gr-foo + +Wireshark PCAP connector, burst tagger, periodic msg source + +```python +install_oot_module( + git_url="https://github.com/bastibl/gr-foo", + branch="maint-3.10", + build_deps=["castxml"] +) +``` + +### Optical + +#### gr-owc + +Optical Wireless Communication channel simulation and modulation + +```python +install_oot_module( + git_url="https://github.com/UCaNLabUMB/gr-owc", + branch="main" +) +``` + +### Analysis + +#### gr-inspector + +Signal analysis toolbox (energy detection, OFDM estimation) + + + +```python +install_oot_module( + git_url="https://github.com/gnuradio/gr-inspector", + branch="master", + build_deps=["qtbase5-dev", "libqwt-qt5-dev"] +) +``` + +## Module Categories Summary + +| Category | Preinstalled | Installable | +|----------|--------------|-------------| +| Hardware | 4 | 0 | +| Satellite | 2 | 2 | +| Cellular | 1 | 0 | +| Broadcast | 1 | 3 | +| Aviation | 1 | 1 | +| IoT | 0 | 2 | +| WiFi | 0 | 1 | +| Analysis | 2 | 1 | +| Visualization | 1 | 0 | +| Utility | 0 | 1 | +| Optical | 0 | 1 | +| **Total** | **12** | **12** | diff --git a/docs/src/content/docs/reference/tools-overview.mdx b/docs/src/content/docs/reference/tools-overview.mdx new file mode 100644 index 0000000..6f21d74 --- /dev/null +++ b/docs/src/content/docs/reference/tools-overview.mdx @@ -0,0 +1,171 @@ +--- +title: Tools Overview +description: Complete reference of all GR-MCP tools +draft: false +--- + +import { CardGrid, Card, Aside } from '@astrojs/starlight/components'; + +GR-MCP exposes tools in two groups: **platform tools** (always available) for flowgraph +design, and **runtime tools** (loaded on demand) for container control. + +## Tool Organization + + + + Always available for flowgraph building, validation, and code generation. + No Docker required. + + + Loaded via `enable_runtime_mode()`. Requires Docker for container features. + + + +## Platform Tools (Always Available) + +### Flowgraph Management + +| Tool | Description | +|------|-------------| +| [`get_blocks`](/reference/tools/flowgraph#get_blocks) | List all blocks in the flowgraph | +| [`make_block`](/reference/tools/flowgraph#make_block) | Create a new block | +| [`remove_block`](/reference/tools/flowgraph#remove_block) | Remove a block | +| [`save_flowgraph`](/reference/tools/flowgraph#save_flowgraph) | Save to `.grc` file | +| [`load_flowgraph`](/reference/tools/flowgraph#load_flowgraph) | Load from `.grc` file | +| [`get_flowgraph_options`](/reference/tools/flowgraph#get_flowgraph_options) | Get flowgraph metadata | +| [`set_flowgraph_options`](/reference/tools/flowgraph#set_flowgraph_options) | Set flowgraph metadata | +| [`export_flowgraph_data`](/reference/tools/flowgraph#export_flowgraph_data) | Export as dict | +| [`import_flowgraph_data`](/reference/tools/flowgraph#import_flowgraph_data) | Import from dict | + +### Block Management + +| Tool | Description | +|------|-------------| +| [`get_block_params`](/reference/tools/blocks#get_block_params) | List block parameters | +| [`set_block_params`](/reference/tools/blocks#set_block_params) | Set block parameters | +| [`get_block_sources`](/reference/tools/blocks#get_block_sources) | List output ports | +| [`get_block_sinks`](/reference/tools/blocks#get_block_sinks) | List input ports | +| [`bypass_block`](/reference/tools/blocks#bypass_block) | Bypass a block | +| [`unbypass_block`](/reference/tools/blocks#unbypass_block) | Unbypass a block | + +### Connection Management + +| Tool | Description | +|------|-------------| +| [`get_connections`](/reference/tools/connections#get_connections) | List all connections | +| [`connect_blocks`](/reference/tools/connections#connect_blocks) | Connect two blocks | +| [`disconnect_blocks`](/reference/tools/connections#disconnect_blocks) | Disconnect two blocks | + +### Validation + +| Tool | Description | +|------|-------------| +| [`validate_block`](/reference/tools/validation#validate_block) | Validate a single block | +| [`validate_flowgraph`](/reference/tools/validation#validate_flowgraph) | Validate entire flowgraph | +| [`get_all_errors`](/reference/tools/validation#get_all_errors) | Get all validation errors | + +### Platform + +| Tool | Description | +|------|-------------| +| [`get_all_available_blocks`](/reference/tools/platform#get_all_available_blocks) | List all block types | +| [`search_blocks`](/reference/tools/platform#search_blocks) | Search blocks by keyword | +| [`get_block_categories`](/reference/tools/platform#get_block_categories) | List block categories | +| [`load_oot_blocks`](/reference/tools/platform#load_oot_blocks) | Load OOT block paths | +| [`add_block_path`](/reference/tools/platform#add_block_path) | Add a block path | +| [`get_block_paths`](/reference/tools/platform#get_block_paths) | List block paths | + +### Code Generation + +| Tool | Description | +|------|-------------| +| [`generate_code`](/reference/tools/codegen#generate_code) | Generate Python code | +| [`evaluate_expression`](/reference/tools/codegen#evaluate_expression) | Evaluate Python expression | +| [`create_embedded_python_block`](/reference/tools/codegen#create_embedded_python_block) | Create embedded Python block | + +## Runtime Tools (Enable with `enable_runtime_mode()`) + + + +### Runtime Mode Control + +| Tool | Description | +|------|-------------| +| [`get_runtime_mode`](/reference/tools/runtime-mode#get_runtime_mode) | Check runtime mode status | +| [`enable_runtime_mode`](/reference/tools/runtime-mode#enable_runtime_mode) | Enable runtime tools | +| [`disable_runtime_mode`](/reference/tools/runtime-mode#disable_runtime_mode) | Disable runtime tools | +| [`get_client_capabilities`](/reference/tools/runtime-mode#get_client_capabilities) | Get MCP client capabilities | +| [`list_client_roots`](/reference/tools/runtime-mode#list_client_roots) | List client root directories | + +### Docker Container Management + +| Tool | Description | +|------|-------------| +| [`launch_flowgraph`](/reference/tools/docker#launch_flowgraph) | Launch in Docker container | +| [`list_containers`](/reference/tools/docker#list_containers) | List running containers | +| [`stop_flowgraph`](/reference/tools/docker#stop_flowgraph) | Stop a container | +| [`remove_flowgraph`](/reference/tools/docker#remove_flowgraph) | Remove a container | +| [`capture_screenshot`](/reference/tools/docker#capture_screenshot) | Capture GUI screenshot | +| [`get_container_logs`](/reference/tools/docker#get_container_logs) | Get container logs | + +### XML-RPC Control + +| Tool | Description | +|------|-------------| +| [`connect`](/reference/tools/xmlrpc#connect) | Connect to XML-RPC URL | +| [`connect_to_container`](/reference/tools/xmlrpc#connect_to_container) | Connect by container name | +| [`disconnect`](/reference/tools/xmlrpc#disconnect) | Disconnect | +| [`get_status`](/reference/tools/xmlrpc#get_status) | Get connection status | +| [`list_variables`](/reference/tools/xmlrpc#list_variables) | List exposed variables | +| [`get_variable`](/reference/tools/xmlrpc#get_variable) | Get variable value | +| [`set_variable`](/reference/tools/xmlrpc#set_variable) | Set variable value | +| [`start`](/reference/tools/xmlrpc#start) | Start flowgraph | +| [`stop`](/reference/tools/xmlrpc#stop) | Stop flowgraph | +| [`lock`](/reference/tools/xmlrpc#lock) | Lock for updates | +| [`unlock`](/reference/tools/xmlrpc#unlock) | Unlock after updates | + +### ControlPort/Thrift + +| Tool | Description | +|------|-------------| +| [`connect_controlport`](/reference/tools/controlport#connect_controlport) | Connect to ControlPort | +| [`connect_to_container_controlport`](/reference/tools/controlport#connect_to_container_controlport) | Connect by container | +| [`disconnect_controlport`](/reference/tools/controlport#disconnect_controlport) | Disconnect ControlPort | +| [`get_knobs`](/reference/tools/controlport#get_knobs) | Get knob values | +| [`set_knobs`](/reference/tools/controlport#set_knobs) | Set knob values | +| [`get_knob_properties`](/reference/tools/controlport#get_knob_properties) | Get knob metadata | +| [`get_performance_counters`](/reference/tools/controlport#get_performance_counters) | Get perf metrics | +| [`post_message`](/reference/tools/controlport#post_message) | Send PMT message | + +### Code Coverage + +| Tool | Description | +|------|-------------| +| [`collect_coverage`](/reference/tools/coverage#collect_coverage) | Collect coverage data | +| [`generate_coverage_report`](/reference/tools/coverage#generate_coverage_report) | Generate report | +| [`combine_coverage`](/reference/tools/coverage#combine_coverage) | Combine multiple runs | +| [`delete_coverage`](/reference/tools/coverage#delete_coverage) | Delete coverage data | + +### OOT Module Installation + +| Tool | Description | +|------|-------------| +| [`detect_oot_modules`](/reference/tools/oot#detect_oot_modules) | Detect OOT dependencies | +| [`install_oot_module`](/reference/tools/oot#install_oot_module) | Install OOT module | +| [`list_oot_images`](/reference/tools/oot#list_oot_images) | List installed images | +| [`remove_oot_image`](/reference/tools/oot#remove_oot_image) | Remove OOT image | +| [`build_multi_oot_image`](/reference/tools/oot#build_multi_oot_image) | Build combo image | +| [`list_combo_images`](/reference/tools/oot#list_combo_images) | List combo images | +| [`remove_combo_image`](/reference/tools/oot#remove_combo_image) | Remove combo image | + +## MCP Resources + +In addition to tools, GR-MCP exposes resources for OOT module discovery: + +| URI | Description | +|-----|-------------| +| `oot://directory` | Index of all OOT modules in the catalog | +| `oot://directory/{name}` | Detailed info for a specific module | diff --git a/docs/src/content/docs/reference/tools/blocks.mdx b/docs/src/content/docs/reference/tools/blocks.mdx new file mode 100644 index 0000000..3045e58 --- /dev/null +++ b/docs/src/content/docs/reference/tools/blocks.mdx @@ -0,0 +1,168 @@ +--- +title: Block Tools +description: Tools for block parameter and port management +draft: false +--- + +Tools for managing block parameters and inspecting input/output ports. + +## `get_block_params` + +Get all parameters for a block. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Name of the block | + +### Returns + +**Type:** `list[ParamModel]` + +List of parameters with key, value, name, and type. + +### Example + +```python +params = get_block_params(block_name="osmosdr_source_0") +# Returns: [ +# ParamModel(key="freq", value="101.1e6", name="Ch0: Frequency", dtype="real"), +# ParamModel(key="sample_rate", value="2e6", name="Sample Rate", dtype="real"), +# ... +# ] +``` + +--- + +## `set_block_params` + +Set parameters on a block. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Name of the block | +| `params` | `dict[str, Any]` | - | Dict of parameter key → value | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +set_block_params(block_name="osmosdr_source_0", params={ + "freq": "101.1e6", + "sample_rate": "2e6", + "gain": "40" +}) +# Returns: True +``` + +--- + +## `get_block_sources` + +Get output ports (sources) for a block. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Name of the block | + +### Returns + +**Type:** `list[PortModel]` + +List of output ports with key, name, and data type. + +### Example + +```python +sources = get_block_sources(block_name="osmosdr_source_0") +# Returns: [ +# PortModel(key="0", name="out", dtype="complex", direction="source") +# ] +``` + +--- + +## `get_block_sinks` + +Get input ports (sinks) for a block. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Name of the block | + +### Returns + +**Type:** `list[PortModel]` + +List of input ports with key, name, and data type. + +### Example + +```python +sinks = get_block_sinks(block_name="low_pass_filter_0") +# Returns: [ +# PortModel(key="0", name="in", dtype="complex", direction="sink") +# ] +``` + +--- + +## `bypass_block` + +Bypass a block (pass signal through without processing). + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Name of the block to bypass | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +bypass_block(block_name="low_pass_filter_0") +# Returns: True +``` + +--- + +## `unbypass_block` + +Re-enable a bypassed block. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Name of the block to unbypass | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +unbypass_block(block_name="low_pass_filter_0") +# Returns: True +``` diff --git a/docs/src/content/docs/reference/tools/codegen.mdx b/docs/src/content/docs/reference/tools/codegen.mdx new file mode 100644 index 0000000..9b1b31f --- /dev/null +++ b/docs/src/content/docs/reference/tools/codegen.mdx @@ -0,0 +1,147 @@ +--- +title: Code Generation +description: Tools for generating Python code and evaluating expressions +draft: false +--- + +Tools for generating executable Python code from flowgraphs. + +## `generate_code` + +Generate Python/C++ code from the current flowgraph. + +Unlike the `grcc` command-line tool, this does **not** block on validation errors. +Validation warnings are included in the response for reference. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `output_dir` | `str` | `""` | Output directory (defaults to temp directory) | + +### Returns + +**Type:** `GeneratedCodeModel` + +Generated code result with path, validity, and warnings. + +### Example + +```python +result = generate_code(output_dir="/tmp") +# Returns: GeneratedCodeModel( +# file_path="/tmp/fm_receiver.py", +# is_valid=True, +# warnings=[] +# ) + +# With validation warnings +result = generate_code() +# Returns: GeneratedCodeModel( +# file_path="/tmp/tmpXXXXXX/fm_receiver.py", +# is_valid=False, +# warnings=["audio_sink_0: Sample rate mismatch"] +# ) +``` + +### Notes + +- Generated code includes XML-RPC server if the flowgraph has XML-RPC variables +- Output type (Python/C++) depends on `generate_options` in flowgraph options +- File name is derived from flowgraph title + +--- + +## `evaluate_expression` + +Evaluate a Python expression in the flowgraph's namespace. + +Useful for testing parameter expressions before setting them. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `expr` | `str` | - | Python expression to evaluate | + +### Returns + +**Type:** `Any` + +Result of the expression evaluation. + +### Example + +```python +# Arithmetic +result = evaluate_expression("101.1e6 + 200e3") +# Returns: 101300000.0 + +# Using numpy (available in flowgraph namespace) +result = evaluate_expression("np.pi * 2") +# Returns: 6.283185307179586 + +# Complex expressions +result = evaluate_expression("int(2e6 / 10)") +# Returns: 200000 +``` + +--- + +## `create_embedded_python_block` + +Create an embedded Python block from source code. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `source_code` | `str` | - | Python source code for the block | +| `block_name` | `str \| None` | `None` | Optional custom block name | + +### Returns + +**Type:** `str` + +The assigned block name. + +### Example + +```python +source = ''' +import numpy as np +from gnuradio import gr + +class my_multiplier(gr.sync_block): + """Multiply input by a constant.""" + + def __init__(self, multiplier=2.0): + gr.sync_block.__init__( + self, + name="my_multiplier", + in_sig=[np.complex64], + out_sig=[np.complex64] + ) + self.multiplier = multiplier + + def work(self, input_items, output_items): + output_items[0][:] = input_items[0] * self.multiplier + return len(output_items[0]) +''' + +name = create_embedded_python_block(source_code=source) +# Returns: "epy_block_0" + +# With custom name +name = create_embedded_python_block( + source_code=source, + block_name="multiplier" +) +# Returns: "multiplier_0" +``` + +### Notes + +- Embedded blocks must inherit from `gr.sync_block`, `gr.decim_block`, or `gr.interp_block` +- The block class must define `__init__` and `work` methods +- Use `set_block_params()` to configure the block after creation diff --git a/docs/src/content/docs/reference/tools/connections.mdx b/docs/src/content/docs/reference/tools/connections.mdx new file mode 100644 index 0000000..f3cd79c --- /dev/null +++ b/docs/src/content/docs/reference/tools/connections.mdx @@ -0,0 +1,113 @@ +--- +title: Connection Tools +description: Tools for wiring blocks together +draft: false +--- + +Tools for managing connections (wires) between blocks. + +## `get_connections` + +List all connections in the flowgraph. + +### Returns + +**Type:** `list[ConnectionModel]` + +List of connections with source/sink block names and port keys. + +### Example + +```python +connections = get_connections() +# Returns: [ +# ConnectionModel( +# source_block="osmosdr_source_0", source_port="0", +# sink_block="low_pass_filter_0", sink_port="0" +# ), +# ... +# ] +``` + +--- + +## `connect_blocks` + +Connect two blocks by their ports. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `source_block_name` | `str` | - | Name of source block | +| `sink_block_name` | `str` | - | Name of sink block | +| `source_port_name` | `str` | - | Port key on source block | +| `sink_port_name` | `str` | - | Port key on sink block | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +connect_blocks( + source_block_name="osmosdr_source_0", + sink_block_name="low_pass_filter_0", + source_port_name="0", + sink_port_name="0" +) +# Returns: True +``` + +### Notes + +- Use `get_block_sources()` and `get_block_sinks()` to discover available ports +- Most blocks use port `"0"` for their primary input/output +- Port types must be compatible (e.g., both complex, both float) + +--- + +## `disconnect_blocks` + +Disconnect two blocks. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `source_port` | `PortModel` | - | Source port to disconnect | +| `sink_port` | `PortModel` | - | Sink port to disconnect | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +# Get the connection to disconnect +connections = get_connections() +conn = connections[0] + +disconnect_blocks( + source_port=PortModel( + parent=conn.source_block, + key=conn.source_port, + name="out", + dtype="complex", + direction="source" + ), + sink_port=PortModel( + parent=conn.sink_block, + key=conn.sink_port, + name="in", + dtype="complex", + direction="sink" + ) +) +``` diff --git a/docs/src/content/docs/reference/tools/controlport.mdx b/docs/src/content/docs/reference/tools/controlport.mdx new file mode 100644 index 0000000..24f5c22 --- /dev/null +++ b/docs/src/content/docs/reference/tools/controlport.mdx @@ -0,0 +1,268 @@ +--- +title: ControlPort Tools +description: Tools for ControlPort/Thrift connection and monitoring +draft: false +--- + +import { Aside } from '@astrojs/starlight/components'; + +Tools for advanced runtime control via ControlPort (Thrift protocol). + + + +## `connect_controlport` + +Connect to a GNU Radio ControlPort/Thrift endpoint. + +ControlPort provides richer functionality than XML-RPC: +- Native type support (complex numbers, vectors) +- Performance counters (throughput, timing, buffer utilization) +- Knob metadata (units, min/max, descriptions) +- PMT message injection +- Regex-based knob queries + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `host` | `str` | `"127.0.0.1"` | Hostname or IP address | +| `port` | `int` | `9090` | ControlPort Thrift port | + +### Returns + +**Type:** `ThriftConnectionInfoModel` + +Connection details. + +### Example + +```python +connect_controlport(host="127.0.0.1", port=9090) +# Returns: ThriftConnectionInfoModel( +# host="127.0.0.1", +# port=9090, +# container_name=None +# ) +``` + +--- + +## `connect_to_container_controlport` + +Connect to a flowgraph's ControlPort by container name. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str` | - | Container name | + +### Returns + +**Type:** `ThriftConnectionInfoModel` + +Connection details. + +### Example + +```python +connect_to_container_controlport(name="fm-profiled") +``` + + + +--- + +## `disconnect_controlport` + +Disconnect from the current ControlPort endpoint. + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +disconnect_controlport() +# Returns: True +``` + +--- + +## `get_knobs` + +Get ControlPort knobs, optionally filtered by regex pattern. + +Knobs are named using the pattern: `block_alias::varname` + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `pattern` | `str` | `""` | Regex pattern for filtering (empty = all) | + +### Returns + +**Type:** `list[KnobModel]` + +List of knobs with name, value, and type. + +### Example + +```python +# Get all knobs +knobs = get_knobs(pattern="") + +# Filter by pattern +knobs = get_knobs(pattern=".*frequency.*") +# Returns: [KnobModel(name="sig_source_0::frequency", value=1000.0, ...)] + +# Get knobs for a specific block +knobs = get_knobs(pattern="osmosdr_source_0::.*") +``` + +--- + +## `set_knobs` + +Set multiple ControlPort knobs atomically. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `knobs` | `dict[str, Any]` | - | Dict mapping knob names to new values | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +set_knobs({ + "sig_source_0::frequency": 1500.0, + "sig_source_0::amplitude": 0.8 +}) +# Returns: True +``` + +--- + +## `get_knob_properties` + +Get metadata (units, min/max, description) for specified knobs. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `names` | `list[str]` | - | Knob names to query (empty = all) | + +### Returns + +**Type:** `list[KnobPropertiesModel]` + +Knob metadata. + +### Example + +```python +props = get_knob_properties(names=["sig_source_0::frequency"]) +# Returns: [KnobPropertiesModel( +# name="sig_source_0::frequency", +# units="Hz", +# min=0.0, +# max=1e9, +# description="Output signal frequency" +# )] +``` + +--- + +## `get_performance_counters` + +Get performance metrics for blocks via ControlPort. + +Requires the flowgraph to be launched with `enable_controlport=True` and +`enable_perf_counters=True` (default). + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block` | `str \| None` | `None` | Block alias to filter (None = all blocks) | + +### Returns + +**Type:** `list[PerfCounterModel]` + +Performance metrics. + +### Example + +```python +counters = get_performance_counters() +# Returns: [PerfCounterModel( +# block="low_pass_filter_0", +# nproduced=1048576, +# nconsumed=10485760, +# avg_work_time_us=45.3, +# avg_throughput=23.2e6, +# pc_input_buffers_full=0.85, +# pc_output_buffers_full=0.12 +# ), ...] + +# Filter by block +counters = get_performance_counters(block="low_pass_filter_0") +``` + +### Metrics + +| Metric | Description | +|--------|-------------| +| `nproduced` | Items produced by block | +| `nconsumed` | Items consumed by block | +| `avg_work_time_us` | Average time in `work()` function | +| `avg_throughput` | Items per second | +| `pc_input_buffers_full` | Input buffer utilization (0-1) | +| `pc_output_buffers_full` | Output buffer utilization (0-1) | + +--- + +## `post_message` + +Send a PMT message to a block's message port via ControlPort. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block` | `str` | - | Block alias (e.g., "msg_sink0") | +| `port` | `str` | - | Message port name (e.g., "in") | +| `message` | `Any` | - | Message to send (converted to PMT if needed) | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +# Send a string message +post_message(block="pdu_sink_0", port="pdus", message="hello") + +# Send a dict (converted to PMT dict) +post_message(block="command_handler_0", port="command", message={"freq": 1e6}) +``` diff --git a/docs/src/content/docs/reference/tools/coverage.mdx b/docs/src/content/docs/reference/tools/coverage.mdx new file mode 100644 index 0000000..0681eff --- /dev/null +++ b/docs/src/content/docs/reference/tools/coverage.mdx @@ -0,0 +1,158 @@ +--- +title: Coverage Tools +description: Tools for collecting Python code coverage +draft: false +--- + +import { Aside } from '@astrojs/starlight/components'; + +Tools for collecting Python code coverage from containerized flowgraphs. + + + +## `collect_coverage` + +Collect coverage data from a stopped container. + +Combines any parallel coverage files and returns a summary. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str` | - | Container name | + +### Returns + +**Type:** `CoverageDataModel` + +Coverage summary. + +### Example + +```python +data = collect_coverage(name="coverage-test") +# Returns: CoverageDataModel( +# container_name="coverage-test", +# coverage_file="/tmp/gr-mcp-coverage/coverage-test/.coverage", +# summary="Name Stmts Miss Cover\n...", +# lines_covered=150, +# lines_total=200, +# coverage_percent=75.0 +# ) +``` + + + +--- + +## `generate_coverage_report` + +Generate a coverage report in the specified format. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str` | - | Container name | +| `format` | `str` | `"html"` | Report format: `html`, `xml`, or `json` | + +### Returns + +**Type:** `CoverageReportModel` + +Report location. + +### Example + +```python +# HTML report +report = generate_coverage_report(name="coverage-test", format="html") +# Returns: CoverageReportModel( +# container_name="coverage-test", +# format="html", +# report_path="/tmp/gr-mcp-coverage/coverage-test/htmlcov/index.html" +# ) + +# XML report (Cobertura format) +report = generate_coverage_report(name="coverage-test", format="xml") +# report_path="/tmp/gr-mcp-coverage/coverage-test/coverage.xml" + +# JSON report +report = generate_coverage_report(name="coverage-test", format="json") +# report_path="/tmp/gr-mcp-coverage/coverage-test/coverage.json" +``` + +--- + +## `combine_coverage` + +Combine coverage data from multiple containers. + +Useful for aggregating coverage across a test suite. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `names` | `list[str]` | - | Container names to combine | + +### Returns + +**Type:** `CoverageDataModel` + +Combined coverage summary. + +### Example + +```python +combined = combine_coverage(names=["test-1", "test-2", "test-3"]) +# Returns: CoverageDataModel( +# container_name="combined", +# coverage_file="/tmp/gr-mcp-coverage/combined/.coverage", +# summary="...", +# lines_covered=250, +# lines_total=300, +# coverage_percent=83.3 +# ) +``` + +--- + +## `delete_coverage` + +Delete coverage data. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str \| None` | `None` | Delete specific container's coverage | +| `older_than_days` | `int \| None` | `None` | Delete coverage older than N days | + +### Returns + +**Type:** `int` + +Number of coverage directories deleted. + +### Example + +```python +# Delete specific container's coverage +delete_coverage(name="coverage-test") +# Returns: 1 + +# Delete old coverage +delete_coverage(older_than_days=7) +# Returns: 5 + +# Delete all coverage +delete_coverage() +# Returns: 10 +``` diff --git a/docs/src/content/docs/reference/tools/docker.mdx b/docs/src/content/docs/reference/tools/docker.mdx new file mode 100644 index 0000000..2373d50 --- /dev/null +++ b/docs/src/content/docs/reference/tools/docker.mdx @@ -0,0 +1,212 @@ +--- +title: Docker Tools +description: Tools for container lifecycle management +draft: false +--- + +import { Aside } from '@astrojs/starlight/components'; + +Tools for launching and managing flowgraphs in Docker containers. + + + +## `launch_flowgraph` + +Launch a flowgraph in a Docker container with Xvfb (headless X11). + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `flowgraph_path` | `str` | - | Path to the `.py` flowgraph file | +| `name` | `str \| None` | `None` | Container name (defaults to `gr-{stem}`) | +| `xmlrpc_port` | `int` | `0` | XML-RPC port (0 = auto-allocate) | +| `enable_vnc` | `bool` | `False` | Enable VNC server for visual debugging | +| `enable_coverage` | `bool` | `False` | Enable Python code coverage collection | +| `enable_controlport` | `bool` | `False` | Enable ControlPort/Thrift | +| `controlport_port` | `int` | `9090` | ControlPort port | +| `enable_perf_counters` | `bool` | `True` | Enable performance counters | +| `device_paths` | `list[str] \| None` | `None` | Host device paths to pass through | +| `image` | `str \| None` | `None` | Docker image to use | +| `auto_image` | `bool` | `False` | Auto-detect OOT modules and build image | + +### Returns + +**Type:** `ContainerModel` + +Container information including name, status, and ports. + +### Example + +```python +# Basic launch +container = launch_flowgraph( + flowgraph_path="/tmp/fm_receiver.py", + name="fm-radio" +) + +# With XML-RPC and VNC +container = launch_flowgraph( + flowgraph_path="/tmp/fm_receiver.py", + name="fm-radio", + xmlrpc_port=8080, + enable_vnc=True +) + +# With SDR hardware access +container = launch_flowgraph( + flowgraph_path="/tmp/fm_receiver.py", + name="fm-hardware", + device_paths=["/dev/bus/usb"] +) + +# Auto-detect OOT modules +container = launch_flowgraph( + flowgraph_path="/tmp/lora_rx.py", + auto_image=True +) +``` + +--- + +## `list_containers` + +List all GR-MCP managed containers. + +### Returns + +**Type:** `list[ContainerModel]` + +List of containers with status and port information. + +### Example + +```python +containers = list_containers() +# Returns: [ +# ContainerModel( +# name="fm-radio", +# status="running", +# xmlrpc_port=32768, +# vnc_port=5900, +# ... +# ), +# ... +# ] +``` + +--- + +## `stop_flowgraph` + +Stop a running flowgraph container (graceful shutdown). + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str` | - | Container name | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +stop_flowgraph(name="fm-radio") +# Returns: True +``` + + + +--- + +## `remove_flowgraph` + +Remove a flowgraph container. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str` | - | Container name | +| `force` | `bool` | `False` | Force remove even if running | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +# Normal removal (must be stopped first) +remove_flowgraph(name="fm-radio") + +# Force removal +remove_flowgraph(name="fm-radio", force=True) +``` + +--- + +## `capture_screenshot` + +Capture a screenshot of the flowgraph's QT GUI. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str \| None` | `None` | Container name (uses active if not specified) | + +### Returns + +**Type:** `ScreenshotModel` + +Screenshot information with path and dimensions. + +### Example + +```python +screenshot = capture_screenshot(name="fm-radio") +# Returns: ScreenshotModel( +# path="/tmp/gr-mcp-screenshots/fm-radio-2024-01-15-14-30-00.png", +# width=1024, +# height=768 +# ) +``` + +--- + +## `get_container_logs` + +Get logs from a flowgraph container. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str \| None` | `None` | Container name (uses active if not specified) | +| `tail` | `int` | `100` | Number of lines to return | + +### Returns + +**Type:** `str` + +Container stdout/stderr output. + +### Example + +```python +logs = get_container_logs(name="fm-radio", tail=50) +# Returns: "Using Volk machine: avx2_64_mmx\nStarting flowgraph...\n..." +``` diff --git a/docs/src/content/docs/reference/tools/flowgraph.mdx b/docs/src/content/docs/reference/tools/flowgraph.mdx new file mode 100644 index 0000000..fbc180f --- /dev/null +++ b/docs/src/content/docs/reference/tools/flowgraph.mdx @@ -0,0 +1,228 @@ +--- +title: Flowgraph Tools +description: Tools for managing flowgraph structure +draft: false +--- + +Tools for managing flowgraph structure: blocks, connections, save/load, and metadata. + +## `get_blocks` + +List all blocks currently in the flowgraph. + +### Returns + +**Type:** `list[BlockModel]` + +List of blocks with their names, keys, and states. + +### Example + +```python +blocks = get_blocks() +# Returns: [ +# BlockModel(name="osmosdr_source_0", key="osmosdr_source", ...), +# BlockModel(name="low_pass_filter_0", key="low_pass_filter", ...), +# ] +``` + +--- + +## `make_block` + +Create a new block in the flowgraph. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Block type key (e.g., "osmosdr_source") | + +### Returns + +**Type:** `str` + +The unique name assigned to the block (e.g., "osmosdr_source_0"). + +### Example + +```python +name = make_block(block_name="osmosdr_source") +# Returns: "osmosdr_source_0" +``` + +--- + +## `remove_block` + +Remove a block from the flowgraph. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Name of the block to remove | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +remove_block(block_name="osmosdr_source_0") +# Returns: True +``` + +--- + +## `save_flowgraph` + +Save the flowgraph to a `.grc` file. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `filepath` | `str` | - | Path to save the `.grc` file | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +save_flowgraph(filepath="/tmp/my_flowgraph.grc") +# Returns: True +``` + +--- + +## `load_flowgraph` + +Load a flowgraph from a `.grc` file, replacing the current flowgraph. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `filepath` | `str` | - | Path to the `.grc` file | + +### Returns + +**Type:** `list[BlockModel]` + +List of blocks in the loaded flowgraph. + +### Example + +```python +blocks = load_flowgraph(filepath="/tmp/my_flowgraph.grc") +# Returns: [BlockModel(...), ...] +``` + +--- + +## `get_flowgraph_options` + +Get the flowgraph-level options (title, author, generate_options, etc.). + +### Returns + +**Type:** `FlowgraphOptionsModel` + +Flowgraph metadata including title, author, category, description, and generate options. + +### Example + +```python +options = get_flowgraph_options() +# Returns: FlowgraphOptionsModel( +# title="FM Receiver", +# author="Your Name", +# generate_options="qt_gui", +# ... +# ) +``` + +--- + +## `set_flowgraph_options` + +Set flowgraph-level options on the 'options' block. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `params` | `dict[str, Any]` | - | Options to set | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +set_flowgraph_options(params={ + "title": "FM Receiver", + "author": "Your Name", + "generate_options": "qt_gui" +}) +# Returns: True +``` + +--- + +## `export_flowgraph_data` + +Export the flowgraph as a nested dict (same format as `.grc` files). + +### Returns + +**Type:** `dict` + +Complete flowgraph data in GRC YAML format. + +### Example + +```python +data = export_flowgraph_data() +# Returns: {"options": {...}, "blocks": [...], "connections": [...]} +``` + +--- + +## `import_flowgraph_data` + +Import flowgraph data from a dict, replacing current contents. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `data` | `dict` | - | Flowgraph data in GRC format | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +import_flowgraph_data(data={ + "options": {"title": "Test"}, + "blocks": [...], + "connections": [...] +}) +# Returns: True +``` diff --git a/docs/src/content/docs/reference/tools/oot.mdx b/docs/src/content/docs/reference/tools/oot.mdx new file mode 100644 index 0000000..2460339 --- /dev/null +++ b/docs/src/content/docs/reference/tools/oot.mdx @@ -0,0 +1,238 @@ +--- +title: OOT Tools +description: Tools for OOT module detection and installation +draft: false +--- + +import { Aside } from '@astrojs/starlight/components'; + +Tools for detecting, installing, and managing Out-of-Tree (OOT) modules. + + + +## `detect_oot_modules` + +Detect which OOT modules a flowgraph requires. + +Analyzes `.py` or `.grc` files to find OOT module dependencies. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `flowgraph_path` | `str` | - | Path to a `.py` or `.grc` flowgraph file | + +### Returns + +**Type:** `OOTDetectionResult` + +Detection results with modules and recommended image. + +### Example + +```python +result = detect_oot_modules(flowgraph_path="lora_rx.py") +# Returns: OOTDetectionResult( +# detected_modules=["lora_sdr"], +# unknown_blocks=[], +# recommended_image="gnuradio-lora_sdr-runtime:latest" +# ) + +# Multiple modules +result = detect_oot_modules(flowgraph_path="multi_protocol_rx.py") +# Returns: OOTDetectionResult( +# detected_modules=["lora_sdr", "adsb"], +# unknown_blocks=[], +# recommended_image="gr-combo-adsb-lora_sdr:latest" +# ) +``` + +--- + +## `install_oot_module` + +Install an OOT module into a Docker image. + +Clones the git repo, compiles with cmake, and creates a reusable Docker image. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `git_url` | `str` | - | Git repository URL | +| `branch` | `str` | `"main"` | Git branch to build from | +| `build_deps` | `list[str] \| None` | `None` | Extra apt packages for compilation | +| `cmake_args` | `list[str] \| None` | `None` | Extra cmake flags | +| `base_image` | `str \| None` | `None` | Base image (default: gnuradio-runtime:latest) | +| `force` | `bool` | `False` | Rebuild even if image exists | + +### Returns + +**Type:** `OOTInstallResult` + +Installation result with image tag. + +### Example + +```python +# Basic install +result = install_oot_module( + git_url="https://github.com/tapparelj/gr-lora_sdr", + branch="master" +) +# Returns: OOTInstallResult( +# success=True, +# module_name="lora_sdr", +# image_tag="gnuradio-lora_sdr-runtime:latest", +# error=None +# ) + +# With build dependencies +result = install_oot_module( + git_url="https://github.com/hboeglen/gr-dab", + branch="maint-3.10", + build_deps=["autoconf", "automake", "libtool", "libfaad-dev"], + cmake_args=["-DENABLE_DOXYGEN=OFF"] +) +``` + +--- + +## `list_oot_images` + +List all installed OOT module images. + +### Returns + +**Type:** `list[OOTImageInfo]` + +List of installed images. + +### Example + +```python +images = list_oot_images() +# Returns: [ +# OOTImageInfo( +# module_name="lora_sdr", +# image_tag="gnuradio-lora_sdr-runtime:latest", +# git_url="https://github.com/tapparelj/gr-lora_sdr", +# branch="master" +# ), +# ... +# ] +``` + +--- + +## `remove_oot_image` + +Remove an OOT module image and its registry entry. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `module_name` | `str` | - | Module name to remove | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +remove_oot_image(module_name="lora_sdr") +# Returns: True +``` + +--- + +## `build_multi_oot_image` + +Combine multiple OOT modules into a single Docker image. + +Missing modules that exist in the catalog are auto-built first. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `module_names` | `list[str]` | - | Module names to combine | +| `force` | `bool` | `False` | Rebuild even if exists | + +### Returns + +**Type:** `ComboImageResult` + +Combo image details. + +### Example + +```python +result = build_multi_oot_image(module_names=["lora_sdr", "adsb"]) +# Returns: ComboImageResult( +# success=True, +# image=ComboImageInfo( +# combo_key="combo:adsb+lora_sdr", +# image_tag="gr-combo-adsb-lora_sdr:latest", +# modules=["adsb", "lora_sdr"] +# ), +# error=None +# ) +``` + +--- + +## `list_combo_images` + +List all combined multi-OOT images. + +### Returns + +**Type:** `list[ComboImageInfo]` + +List of combo images. + +### Example + +```python +combos = list_combo_images() +# Returns: [ +# ComboImageInfo( +# combo_key="combo:adsb+lora_sdr", +# image_tag="gr-combo-adsb-lora_sdr:latest", +# modules=["adsb", "lora_sdr"] +# ), +# ... +# ] +``` + +--- + +## `remove_combo_image` + +Remove a combined image by its combo key. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `combo_key` | `str` | - | Combo key (e.g., "combo:adsb+lora_sdr") | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +remove_combo_image(combo_key="combo:adsb+lora_sdr") +# Returns: True +``` diff --git a/docs/src/content/docs/reference/tools/platform.mdx b/docs/src/content/docs/reference/tools/platform.mdx new file mode 100644 index 0000000..4343894 --- /dev/null +++ b/docs/src/content/docs/reference/tools/platform.mdx @@ -0,0 +1,173 @@ +--- +title: Platform Tools +description: Tools for discovering blocks and managing paths +draft: false +--- + +Tools for discovering available blocks and managing OOT block paths. + +## `get_all_available_blocks` + +List all block types available on the platform. + +### Returns + +**Type:** `list[BlockTypeModel]` + +List of block types with key and category. + +### Example + +```python +blocks = get_all_available_blocks() +# Returns: [ +# BlockTypeModel(key="osmosdr_source", category="OsmoSDR"), +# BlockTypeModel(key="analog_sig_source_x", category="Waveform Generators"), +# ... +# ] +``` + +--- + +## `search_blocks` + +Search available blocks by keyword and/or category. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `query` | `str` | `""` | Search keyword | +| `category` | `str \| None` | `None` | Filter by category | + +### Returns + +**Type:** `list[BlockTypeDetailModel]` + +Matching blocks with full details. + +### Example + +```python +# Search by keyword +blocks = search_blocks(query="filter") +# Returns: [BlockTypeDetailModel(key="low_pass_filter", ...), ...] + +# Search by category +blocks = search_blocks(category="Audio") +# Returns: [BlockTypeDetailModel(key="audio_sink", ...), ...] + +# Combined search +blocks = search_blocks(query="sink", category="Audio") +``` + +--- + +## `get_block_categories` + +Get all block categories with their block keys. + +### Returns + +**Type:** `dict[str, list[str]]` + +Dict mapping category name to list of block keys. + +### Example + +```python +categories = get_block_categories() +# Returns: { +# "Audio": ["audio_sink", "audio_source"], +# "OsmoSDR": ["osmosdr_source", "osmosdr_sink"], +# "Filters": ["low_pass_filter", "high_pass_filter", ...], +# ... +# } +``` + +--- + +## `load_oot_blocks` + +Load OOT (Out-of-Tree) block paths into the platform. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `paths` | `list[str]` | - | Directories containing `.block.yml` files | + +### Returns + +**Type:** `dict` + +Result with added_paths, invalid_paths, and block counts. + +### Example + +```python +result = load_oot_blocks(paths=[ + "/usr/local/share/gnuradio/grc/blocks", + "/home/user/gr-modules/lib/grc" +]) +# Returns: { +# "added_paths": ["/usr/local/share/gnuradio/grc/blocks"], +# "invalid_paths": ["/home/user/gr-modules/lib/grc"], +# "blocks_before": 200, +# "blocks_after": 215 +# } +``` + +--- + +## `add_block_path` + +Add a single directory containing OOT module block YAML files. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `path` | `str` | - | Directory path | + +### Returns + +**Type:** `BlockPathsModel` + +Updated paths and block count. + +### Example + +```python +result = add_block_path(path="/usr/local/share/gnuradio/grc/blocks") +# Returns: BlockPathsModel( +# paths=[...], +# total_blocks=215, +# blocks_added=15 +# ) +``` + +--- + +## `get_block_paths` + +Show current OOT block paths and total block count. + +### Returns + +**Type:** `BlockPathsModel` + +Current paths configuration. + +### Example + +```python +paths = get_block_paths() +# Returns: BlockPathsModel( +# paths=[ +# "/usr/share/gnuradio/grc/blocks", +# "/usr/local/share/gnuradio/grc/blocks" +# ], +# total_blocks=215 +# ) +``` diff --git a/docs/src/content/docs/reference/tools/runtime-mode.mdx b/docs/src/content/docs/reference/tools/runtime-mode.mdx new file mode 100644 index 0000000..76b07c6 --- /dev/null +++ b/docs/src/content/docs/reference/tools/runtime-mode.mdx @@ -0,0 +1,170 @@ +--- +title: Runtime Mode +description: Tools for enabling runtime features and checking capabilities +draft: false +--- + +import { Aside } from '@astrojs/starlight/components'; + +Tools for managing runtime mode and inspecting MCP client capabilities. + + + +## `get_runtime_mode` + +Check if runtime mode is enabled and what capabilities are available. + +### Returns + +**Type:** `RuntimeModeStatus` + +Status including enabled state, registered tools, and availability. + +### Example + +```python +status = get_runtime_mode() +# Returns: RuntimeModeStatus( +# enabled=False, +# tools_registered=[], +# docker_available=True, +# oot_available=True +# ) +``` + +--- + +## `enable_runtime_mode` + +Enable runtime mode, registering all runtime control tools. + +This adds tools for: +- XML-RPC connection and variable control +- ControlPort/Thrift for performance monitoring +- Docker container lifecycle (if Docker available) +- OOT module installation (if Docker available) + +### Returns + +**Type:** `RuntimeModeStatus` + +Updated status with newly registered tools. + +### Example + +```python +status = enable_runtime_mode() +# Returns: RuntimeModeStatus( +# enabled=True, +# tools_registered=[ +# "launch_flowgraph", "list_containers", "stop_flowgraph", +# "connect", "list_variables", "get_variable", "set_variable", +# ... +# ], +# docker_available=True, +# oot_available=True +# ) +``` + +### Notes + +- Calling when already enabled is a no-op (returns current status) +- If Docker is unavailable, container-related tools are not registered +- If Docker is unavailable, OOT tools are not registered + +--- + +## `disable_runtime_mode` + +Disable runtime mode, removing runtime tools to reduce context. + +Use this when you're done with runtime operations and want to reduce the tool list +for flowgraph design work. + +### Returns + +**Type:** `RuntimeModeStatus` + +Updated status showing disabled state. + +### Example + +```python +status = disable_runtime_mode() +# Returns: RuntimeModeStatus( +# enabled=False, +# tools_registered=[], +# docker_available=True, +# oot_available=True +# ) +``` + +--- + +## `get_client_capabilities` + +Get the connected MCP client's capabilities. + +Useful for debugging MCP connections and understanding what features the client supports. + +### Returns + +**Type:** `ClientCapabilities` + +Client information and capability flags. + +### Example + +```python +caps = get_client_capabilities() +# Returns: ClientCapabilities( +# client_name="claude-code", +# client_version="2.1.15", +# protocol_version="2024-11-05", +# roots=RootsCapability(supported=True, list_changed=True), +# sampling=SamplingCapability(supported=True, tools=True, context=False), +# elicitation=ElicitationCapability(supported=False, form=False, url=False), +# raw_capabilities={"roots": {"listChanged": True}, ...}, +# experimental={} +# ) +``` + +### Capability Explanations + +| Capability | Description | +|------------|-------------| +| `roots` | Client exposes workspace directories | +| `sampling` | Server can request LLM completions | +| `elicitation` | Server can prompt user for input | + +--- + +## `list_client_roots` + +List the root directories advertised by the MCP client. + +Roots represent project directories or workspaces the client wants the server to be aware of. + +### Returns + +**Type:** `list[ClientRoot]` + +List of root directories. + +### Example + +```python +roots = list_client_roots() +# Returns: [ +# ClientRoot(uri="file:///home/user/project", name="project"), +# ClientRoot(uri="file:///home/user/gr-mcp", name="gr-mcp"), +# ] +``` + +### Notes + +- Returns empty list if roots capability is not supported +- Typically includes the current working directory diff --git a/docs/src/content/docs/reference/tools/validation.mdx b/docs/src/content/docs/reference/tools/validation.mdx new file mode 100644 index 0000000..1d76685 --- /dev/null +++ b/docs/src/content/docs/reference/tools/validation.mdx @@ -0,0 +1,98 @@ +--- +title: Validation Tools +description: Tools for checking flowgraph validity +draft: false +--- + +Tools for validating blocks and flowgraphs. + +## `validate_block` + +Validate a single block's configuration. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `block_name` | `str` | - | Name of the block to validate | + +### Returns + +**Type:** `bool` + +True if the block is valid. + +### Example + +```python +is_valid = validate_block(block_name="osmosdr_source_0") +# Returns: True +``` + +--- + +## `validate_flowgraph` + +Validate the entire flowgraph. + +Checks all blocks and connections for errors. + +### Returns + +**Type:** `bool` + +True if the flowgraph is valid. + +### Example + +```python +is_valid = validate_flowgraph() +# Returns: True +``` + +### Notes + +- Validation checks parameter types, required connections, and compatibility +- Use `get_all_errors()` to see specific validation failures +- `generate_code()` does not require validation to pass + +--- + +## `get_all_errors` + +Get all validation errors from the flowgraph. + +### Returns + +**Type:** `list[ErrorModel]` + +List of errors with block name, parameter, and message. + +### Example + +```python +errors = get_all_errors() +# Returns: [ +# ErrorModel( +# block="audio_sink_0", +# param="samp_rate", +# message="Sample rate 50000 not supported by audio device" +# ), +# ... +# ] + +# Empty list means no errors +errors = get_all_errors() +# Returns: [] +``` + +### Error Types + +Common validation errors: + +| Error Type | Example | +|------------|---------| +| Missing connection | "Block has unconnected input port" | +| Type mismatch | "Cannot connect complex to float" | +| Invalid parameter | "Invalid sample rate value" | +| Missing dependency | "Block requires gr-osmosdr" | diff --git a/docs/src/content/docs/reference/tools/xmlrpc.mdx b/docs/src/content/docs/reference/tools/xmlrpc.mdx new file mode 100644 index 0000000..fa8ea0d --- /dev/null +++ b/docs/src/content/docs/reference/tools/xmlrpc.mdx @@ -0,0 +1,263 @@ +--- +title: XML-RPC Tools +description: Tools for XML-RPC connection and variable control +draft: false +--- + +import { Aside } from '@astrojs/starlight/components'; + +Tools for connecting to running flowgraphs via XML-RPC and controlling variables. + + + +## `connect` + +Connect to a GNU Radio XML-RPC endpoint by URL. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `url` | `str` | - | XML-RPC URL (e.g., `http://localhost:8080`) | + +### Returns + +**Type:** `ConnectionInfoModel` + +Connection details including URL and port. + +### Example + +```python +connect(url="http://localhost:8080") +# Returns: ConnectionInfoModel( +# url="http://localhost:8080", +# xmlrpc_port=8080, +# container_name=None +# ) +``` + +--- + +## `connect_to_container` + +Connect to a flowgraph by container name (resolves port automatically). + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str` | - | Container name | + +### Returns + +**Type:** `ConnectionInfoModel` + +Connection details. + +### Example + +```python +connect_to_container(name="fm-radio") +# Returns: ConnectionInfoModel( +# url="http://localhost:32768", +# xmlrpc_port=32768, +# container_name="fm-radio" +# ) +``` + +--- + +## `disconnect` + +Disconnect from the current XML-RPC (and ControlPort) endpoint. + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +disconnect() +# Returns: True +``` + +--- + +## `get_status` + +Get runtime status including connection and container info. + +### Returns + +**Type:** `RuntimeStatusModel` + +Current runtime state. + +### Example + +```python +status = get_status() +# Returns: RuntimeStatusModel( +# connected=True, +# connection=ConnectionInfoModel(...), +# containers=[ContainerModel(...), ...] +# ) +``` + +--- + +## `list_variables` + +List all XML-RPC-exposed variables. + +### Returns + +**Type:** `list[VariableModel]` + +List of variables with name, value, and type. + +### Example + +```python +vars = list_variables() +# Returns: [ +# VariableModel(name="freq", value=101100000.0, type="float"), +# VariableModel(name="gain", value=40, type="int"), +# VariableModel(name="samp_rate", value=2000000.0, type="float"), +# ] +``` + +--- + +## `get_variable` + +Get a variable value. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str` | - | Variable name | + +### Returns + +**Type:** `Any` + +Current variable value. + +### Example + +```python +freq = get_variable(name="freq") +# Returns: 101100000.0 +``` + +--- + +## `set_variable` + +Set a variable value. + +### Parameters + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `str` | - | Variable name | +| `value` | `Any` | - | New value | + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +set_variable(name="freq", value=98.5e6) +# Returns: True +``` + +--- + +## `start` + +Start the connected flowgraph. + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +start() +# Returns: True +``` + +--- + +## `stop` + +Stop the connected flowgraph. + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +stop() +# Returns: True +``` + +--- + +## `lock` + +Lock the flowgraph for thread-safe parameter updates. + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +lock() +set_variable(name="freq", value=102.7e6) +set_variable(name="gain", value=35) +unlock() +``` + +--- + +## `unlock` + +Unlock the flowgraph after parameter updates. + +### Returns + +**Type:** `bool` + +True if successful. + +### Example + +```python +unlock() +# Returns: True +``` diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css new file mode 100644 index 0000000..eb230fa --- /dev/null +++ b/docs/src/styles/custom.css @@ -0,0 +1,63 @@ +/* GR-MCP Documentation Theme */ + +:root { + /* Brand colors - no purple, clean tech aesthetic */ + --sl-color-accent-low: #1e3a5f; + --sl-color-accent: #0ea5e9; + --sl-color-accent-high: #7dd3fc; + + /* Text colors */ + --sl-color-white: #f8fafc; + --sl-color-gray-1: #e2e8f0; + --sl-color-gray-2: #cbd5e1; + --sl-color-gray-3: #94a3b8; + --sl-color-gray-4: #64748b; + --sl-color-gray-5: #475569; + --sl-color-gray-6: #1e293b; + --sl-color-black: #0f172a; +} + +/* Dark mode refinements */ +:root[data-theme='dark'] { + --sl-color-bg: #0f172a; + --sl-color-bg-nav: #1e293b; + --sl-color-bg-sidebar: #1e293b; +} + +/* Code blocks - terminal aesthetic */ +.expressive-code { + --ec-brdRad: 6px; +} + +/* Headings - clean and readable */ +h1, h2, h3, h4 { + font-weight: 600; + letter-spacing: -0.01em; +} + +/* Links in prose */ +.sl-markdown-content a:not(.card-grid a) { + text-decoration-thickness: 1px; + text-underline-offset: 2px; +} + +/* Tables - better contrast */ +table { + width: 100%; +} + +th { + background: var(--sl-color-gray-6); + font-weight: 600; +} + +/* Callouts/asides - consistent with brand */ +.starlight-aside--tip { + --sl-color-asides-text-accent: var(--sl-color-accent); + border-color: var(--sl-color-accent); +} + +/* Hero section tweaks */ +.hero { + padding-block: 3rem; +} diff --git a/examples/combo_test_adsb_lora.py b/examples/combo_test_adsb_lora.py new file mode 100644 index 0000000..d132cd9 --- /dev/null +++ b/examples/combo_test_adsb_lora.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Minimal flowgraph that uses blocks from both adsb and lora_sdr. + +Verifies combo Docker images contain working blocks from multiple +OOT modules. Runs for 5 seconds then exits cleanly. +""" + +import signal +import sys +import time +from xmlrpc.server import SimpleXMLRPCServer +import threading + +from gnuradio import gr, blocks, analog + +# Import OOT modules to verify they're available +from gnuradio import adsb as gr_adsb +from gnuradio import lora_sdr as gr_lora_sdr + + +class combo_test(gr.top_block): + def __init__(self): + gr.top_block.__init__(self, "Combo Test: ADS-B + LoRa SDR") + + ################################################## + # Variables + ################################################## + self.samp_rate = samp_rate = 2e6 + + ################################################## + # ADS-B: noise(float) -> throttle -> demod -> null + ################################################## + self.noise_adsb = analog.noise_source_f(analog.GR_GAUSSIAN, 0.01, 0) + self.throttle_adsb = blocks.throttle(gr.sizeof_float, samp_rate, True) + self.adsb_demod = gr_adsb.demod(samp_rate) + self.null_adsb = blocks.null_sink(gr.sizeof_float) + + self.connect(self.noise_adsb, self.throttle_adsb, self.adsb_demod, self.null_adsb) + + ################################################## + # LoRa: tx -> throttle -> null + ################################################## + self.lora_tx = gr_lora_sdr.lora_sdr_lora_tx( + samp_rate=125000, + bw=125000, + sf=7, + impl_head=True, + cr=1, + has_crc=True, + ldro_mode=2, + frame_zero_padd=128, + ) + self.throttle_lora = blocks.throttle(gr.sizeof_gr_complex, 125000, True) + self.null_lora = blocks.null_sink(gr.sizeof_gr_complex) + + self.connect(self.lora_tx, self.throttle_lora, self.null_lora) + + ################################################## + # XML-RPC for runtime control + ################################################## + self.xmlrpc_port = 8080 + self.xmlrpc_server = SimpleXMLRPCServer( + ("0.0.0.0", self.xmlrpc_port), + allow_none=True, + logRequests=False, + ) + self.xmlrpc_server.register_instance(self) + threading.Thread( + target=self.xmlrpc_server.serve_forever, + daemon=True, + ).start() + + +def main(): + tb = combo_test() + print(f"[combo_test] Starting flowgraph with ADS-B + LoRa SDR blocks") + print(f"[combo_test] XML-RPC on port {tb.xmlrpc_port}") + print(f"[combo_test] ADS-B demod: {type(tb.adsb_demod).__name__}") + print(f"[combo_test] LoRa TX: {type(tb.lora_tx).__name__}") + sys.stdout.flush() + tb.start() + + def sig_handler(sig, frame): + tb.stop() + tb.wait() + sys.exit(0) + + signal.signal(signal.SIGTERM, sig_handler) + signal.signal(signal.SIGINT, sig_handler) + + # Run for a bit then exit + time.sleep(5) + print("[combo_test] Flowgraph ran successfully for 5s, stopping") + sys.stdout.flush() + tb.stop() + tb.wait() + + +if __name__ == "__main__": + main() diff --git a/examples/lora_channel_scanner.grc b/examples/lora_channel_scanner.grc new file mode 100644 index 0000000..83ad5e6 --- /dev/null +++ b/examples/lora_channel_scanner.grc @@ -0,0 +1,540 @@ +options: + parameters: + author: gr-mcp + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: Scans US ISM 915MHz band for LoRa activity. Retune via XML-RPC. + gen_cmake: 'On' + gen_linking: dynamic + generate_options: no_gui + hier_block_src_path: '.:' + id: lora_channel_scanner + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: prompt + sizing_mode: fixed + thread_safe_setters: '' + title: LoRa Channel Scanner + window_size: (1000,1000) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: center_freq + id: variable + parameters: + comment: '' + value: 915e6 + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: channel_index + id: variable + parameters: + comment: '' + value: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_bw + id: variable + parameters: + comment: '' + value: '125000' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_cr + id: variable + parameters: + comment: '' + value: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_sf + id: variable + parameters: + comment: '' + value: '7' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: rf_gain + id: variable + parameters: + comment: '' + value: '40' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: '1000000' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_message_debug_0 + id: blocks_message_debug + parameters: + affinity: '' + alias: '' + comment: '' + en_uvec: 'True' + log_level: info + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_rx_0 + id: lora_rx + parameters: + affinity: '' + alias: '' + bw: lora_bw + comment: '' + cr: lora_cr + has_crc: 'True' + impl_head: 'False' + ldro: '2' + maxoutbuf: '0' + minoutbuf: '0' + pay_len: '255' + print_rx: '[True,True]' + samp_rate: int(samp_rate/2) + sf: lora_sf + soft_decoding: 'True' + sync_word: '[0x12]' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: low_pass_filter_0 + id: low_pass_filter + parameters: + affinity: '' + alias: '' + beta: '6.76' + comment: '' + cutoff_freq: 200e3 + decim: '2' + gain: '1' + interp: '1' + maxoutbuf: '0' + minoutbuf: '0' + samp_rate: samp_rate + type: fir_filter_ccf + width: 50e3 + win: window.WIN_HAMMING + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: osmosdr_source_0 + id: osmosdr_source + parameters: + affinity: '' + alias: '' + ant0: '' + ant1: '' + ant10: '' + ant11: '' + ant12: '' + ant13: '' + ant14: '' + ant15: '' + ant16: '' + ant17: '' + ant18: '' + ant19: '' + ant2: '' + ant20: '' + ant21: '' + ant22: '' + ant23: '' + ant24: '' + ant25: '' + ant26: '' + ant27: '' + ant28: '' + ant29: '' + ant3: '' + ant30: '' + ant31: '' + ant4: '' + ant5: '' + ant6: '' + ant7: '' + ant8: '' + ant9: '' + args: rtl=0 + bb_gain0: '20' + bb_gain1: '20' + bb_gain10: '20' + bb_gain11: '20' + bb_gain12: '20' + bb_gain13: '20' + bb_gain14: '20' + bb_gain15: '20' + bb_gain16: '20' + bb_gain17: '20' + bb_gain18: '20' + bb_gain19: '20' + bb_gain2: '20' + bb_gain20: '20' + bb_gain21: '20' + bb_gain22: '20' + bb_gain23: '20' + bb_gain24: '20' + bb_gain25: '20' + bb_gain26: '20' + bb_gain27: '20' + bb_gain28: '20' + bb_gain29: '20' + bb_gain3: '20' + bb_gain30: '20' + bb_gain31: '20' + bb_gain4: '20' + bb_gain5: '20' + bb_gain6: '20' + bb_gain7: '20' + bb_gain8: '20' + bb_gain9: '20' + bw0: '0' + bw1: '0' + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' + comment: '' + corr0: '0' + corr1: '0' + corr10: '0' + corr11: '0' + corr12: '0' + corr13: '0' + corr14: '0' + corr15: '0' + corr16: '0' + corr17: '0' + corr18: '0' + corr19: '0' + corr2: '0' + corr20: '0' + corr21: '0' + corr22: '0' + corr23: '0' + corr24: '0' + corr25: '0' + corr26: '0' + corr27: '0' + corr28: '0' + corr29: '0' + corr3: '0' + corr30: '0' + corr31: '0' + corr4: '0' + corr5: '0' + corr6: '0' + corr7: '0' + corr8: '0' + corr9: '0' + dc_offset_mode0: '2' + dc_offset_mode1: '0' + dc_offset_mode10: '0' + dc_offset_mode11: '0' + dc_offset_mode12: '0' + dc_offset_mode13: '0' + dc_offset_mode14: '0' + dc_offset_mode15: '0' + dc_offset_mode16: '0' + dc_offset_mode17: '0' + dc_offset_mode18: '0' + dc_offset_mode19: '0' + dc_offset_mode2: '0' + dc_offset_mode20: '0' + dc_offset_mode21: '0' + dc_offset_mode22: '0' + dc_offset_mode23: '0' + dc_offset_mode24: '0' + dc_offset_mode25: '0' + dc_offset_mode26: '0' + dc_offset_mode27: '0' + dc_offset_mode28: '0' + dc_offset_mode29: '0' + dc_offset_mode3: '0' + dc_offset_mode30: '0' + dc_offset_mode31: '0' + dc_offset_mode4: '0' + dc_offset_mode5: '0' + dc_offset_mode6: '0' + dc_offset_mode7: '0' + dc_offset_mode8: '0' + dc_offset_mode9: '0' + freq0: center_freq + freq1: 100e6 + freq10: 100e6 + freq11: 100e6 + freq12: 100e6 + freq13: 100e6 + freq14: 100e6 + freq15: 100e6 + freq16: 100e6 + freq17: 100e6 + freq18: 100e6 + freq19: 100e6 + freq2: 100e6 + freq20: 100e6 + freq21: 100e6 + freq22: 100e6 + freq23: 100e6 + freq24: 100e6 + freq25: 100e6 + freq26: 100e6 + freq27: 100e6 + freq28: 100e6 + freq29: 100e6 + freq3: 100e6 + freq30: 100e6 + freq31: 100e6 + freq4: 100e6 + freq5: 100e6 + freq6: 100e6 + freq7: 100e6 + freq8: 100e6 + freq9: 100e6 + gain0: rf_gain + gain1: '10' + gain10: '10' + gain11: '10' + gain12: '10' + gain13: '10' + gain14: '10' + gain15: '10' + gain16: '10' + gain17: '10' + gain18: '10' + gain19: '10' + gain2: '10' + gain20: '10' + gain21: '10' + gain22: '10' + gain23: '10' + gain24: '10' + gain25: '10' + gain26: '10' + gain27: '10' + gain28: '10' + gain29: '10' + gain3: '10' + gain30: '10' + gain31: '10' + gain4: '10' + gain5: '10' + gain6: '10' + gain7: '10' + gain8: '10' + gain9: '10' + gain_mode0: 'False' + gain_mode1: 'False' + gain_mode10: 'False' + gain_mode11: 'False' + gain_mode12: 'False' + gain_mode13: 'False' + gain_mode14: 'False' + gain_mode15: 'False' + gain_mode16: 'False' + gain_mode17: 'False' + gain_mode18: 'False' + gain_mode19: 'False' + gain_mode2: 'False' + gain_mode20: 'False' + gain_mode21: 'False' + gain_mode22: 'False' + gain_mode23: 'False' + gain_mode24: 'False' + gain_mode25: 'False' + gain_mode26: 'False' + gain_mode27: 'False' + gain_mode28: 'False' + gain_mode29: 'False' + gain_mode3: 'False' + gain_mode30: 'False' + gain_mode31: 'False' + gain_mode4: 'False' + gain_mode5: 'False' + gain_mode6: 'False' + gain_mode7: 'False' + gain_mode8: 'False' + gain_mode9: 'False' + if_gain0: '20' + if_gain1: '20' + if_gain10: '20' + if_gain11: '20' + if_gain12: '20' + if_gain13: '20' + if_gain14: '20' + if_gain15: '20' + if_gain16: '20' + if_gain17: '20' + if_gain18: '20' + if_gain19: '20' + if_gain2: '20' + if_gain20: '20' + if_gain21: '20' + if_gain22: '20' + if_gain23: '20' + if_gain24: '20' + if_gain25: '20' + if_gain26: '20' + if_gain27: '20' + if_gain28: '20' + if_gain29: '20' + if_gain3: '20' + if_gain30: '20' + if_gain31: '20' + if_gain4: '20' + if_gain5: '20' + if_gain6: '20' + if_gain7: '20' + if_gain8: '20' + if_gain9: '20' + iq_balance_mode0: '2' + iq_balance_mode1: '0' + iq_balance_mode10: '0' + iq_balance_mode11: '0' + iq_balance_mode12: '0' + iq_balance_mode13: '0' + iq_balance_mode14: '0' + iq_balance_mode15: '0' + iq_balance_mode16: '0' + iq_balance_mode17: '0' + iq_balance_mode18: '0' + iq_balance_mode19: '0' + iq_balance_mode2: '0' + iq_balance_mode20: '0' + iq_balance_mode21: '0' + iq_balance_mode22: '0' + iq_balance_mode23: '0' + iq_balance_mode24: '0' + iq_balance_mode25: '0' + iq_balance_mode26: '0' + iq_balance_mode27: '0' + iq_balance_mode28: '0' + iq_balance_mode29: '0' + iq_balance_mode3: '0' + iq_balance_mode30: '0' + iq_balance_mode31: '0' + iq_balance_mode4: '0' + iq_balance_mode5: '0' + iq_balance_mode6: '0' + iq_balance_mode7: '0' + iq_balance_mode8: '0' + iq_balance_mode9: '0' + maxoutbuf: '0' + minoutbuf: '0' + nchan: '1' + num_mboards: '1' + sample_rate: samp_rate + sync: sync + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: xmlrpc_server_0 + id: xmlrpc_server + parameters: + addr: localhost + alias: '' + comment: '' + port: '8080' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled + +connections: +- [lora_rx_0, out, blocks_message_debug_0, print] +- [low_pass_filter_0, '0', lora_rx_0, '0'] +- [osmosdr_source_0, '0', low_pass_filter_0, '0'] + +metadata: + file_format: 1 + grc_version: 3.10.12.0 diff --git a/examples/lora_channel_scanner.py b/examples/lora_channel_scanner.py new file mode 100755 index 0000000..c8ba1ae --- /dev/null +++ b/examples/lora_channel_scanner.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# +# SPDX-License-Identifier: GPL-3.0 +# +# GNU Radio Python Flow Graph +# Title: LoRa Channel Scanner +# Author: gr-mcp +# Description: Scans US ISM 915MHz band for LoRa activity. Retune via XML-RPC. +# GNU Radio version: 3.10.12.0 + +from gnuradio import blocks, gr +from gnuradio import filter +from gnuradio.filter import firdes +from gnuradio import gr +from gnuradio.fft import window +import sys +import signal +from argparse import ArgumentParser +from gnuradio.eng_arg import eng_float, intx +from gnuradio import eng_notation +from xmlrpc.server import SimpleXMLRPCServer +import threading +import gnuradio.lora_sdr as lora_sdr +import osmosdr +import time + + + + +class lora_channel_scanner(gr.top_block): + + def __init__(self): + gr.top_block.__init__(self, "LoRa Channel Scanner", catch_exceptions=True) + self.flowgraph_started = threading.Event() + + ################################################## + # Variables + ################################################## + self.samp_rate = samp_rate = 1000000 + self.rf_gain = rf_gain = 40 + self.lora_sf = lora_sf = 7 + self.lora_cr = lora_cr = 1 + self.lora_bw = lora_bw = 125000 + self.channel_index = channel_index = 0 + self.center_freq = center_freq = 915e6 + + ################################################## + # Blocks + ################################################## + + self.xmlrpc_server_0 = SimpleXMLRPCServer(('localhost', 8080), allow_none=True) + self.xmlrpc_server_0.register_instance(self) + self.xmlrpc_server_0_thread = threading.Thread(target=self.xmlrpc_server_0.serve_forever) + self.xmlrpc_server_0_thread.daemon = True + self.xmlrpc_server_0_thread.start() + self.osmosdr_source_0 = osmosdr.source( + args="numchan=" + str(1) + " " + 'rtl=0' + ) + self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t()) + self.osmosdr_source_0.set_sample_rate(samp_rate) + self.osmosdr_source_0.set_center_freq(center_freq, 0) + self.osmosdr_source_0.set_freq_corr(0, 0) + self.osmosdr_source_0.set_dc_offset_mode(2, 0) + self.osmosdr_source_0.set_iq_balance_mode(2, 0) + self.osmosdr_source_0.set_gain_mode(False, 0) + self.osmosdr_source_0.set_gain(rf_gain, 0) + self.osmosdr_source_0.set_if_gain(20, 0) + self.osmosdr_source_0.set_bb_gain(20, 0) + self.osmosdr_source_0.set_antenna('', 0) + self.osmosdr_source_0.set_bandwidth(0, 0) + self.low_pass_filter_0 = filter.fir_filter_ccf( + 2, + firdes.low_pass( + 1, + samp_rate, + 200e3, + 50e3, + window.WIN_HAMMING, + 6.76)) + self.lora_rx_0 = lora_sdr.lora_sdr_lora_rx( bw=lora_bw, cr=1, has_crc=True, impl_head=False, pay_len=255, samp_rate=(int(samp_rate/2)), sf=lora_sf, sync_word=[0x12], soft_decoding=True, ldro_mode=2, print_rx=[True,True]) + self.blocks_message_debug_0 = blocks.message_debug(True, gr.log_levels.info) + + + ################################################## + # Connections + ################################################## + self.msg_connect((self.lora_rx_0, 'out'), (self.blocks_message_debug_0, 'print')) + self.connect((self.low_pass_filter_0, 0), (self.lora_rx_0, 0)) + self.connect((self.osmosdr_source_0, 0), (self.low_pass_filter_0, 0)) + + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + self.low_pass_filter_0.set_taps(firdes.low_pass(1, self.samp_rate, 200e3, 50e3, window.WIN_HAMMING, 6.76)) + self.osmosdr_source_0.set_sample_rate(self.samp_rate) + + def get_rf_gain(self): + return self.rf_gain + + def set_rf_gain(self, rf_gain): + self.rf_gain = rf_gain + self.osmosdr_source_0.set_gain(self.rf_gain, 0) + + def get_lora_sf(self): + return self.lora_sf + + def set_lora_sf(self, lora_sf): + self.lora_sf = lora_sf + + def get_lora_cr(self): + return self.lora_cr + + def set_lora_cr(self, lora_cr): + self.lora_cr = lora_cr + + def get_lora_bw(self): + return self.lora_bw + + def set_lora_bw(self, lora_bw): + self.lora_bw = lora_bw + + def get_channel_index(self): + return self.channel_index + + def set_channel_index(self, channel_index): + self.channel_index = channel_index + + def get_center_freq(self): + return self.center_freq + + def set_center_freq(self, center_freq): + self.center_freq = center_freq + self.osmosdr_source_0.set_center_freq(self.center_freq, 0) + + + + +def main(top_block_cls=lora_channel_scanner, options=None): + tb = top_block_cls() + + def sig_handler(sig=None, frame=None): + tb.stop() + tb.wait() + + sys.exit(0) + + signal.signal(signal.SIGINT, sig_handler) + signal.signal(signal.SIGTERM, sig_handler) + + tb.start() + tb.flowgraph_started.set() + + try: + input('Press Enter to quit: ') + except EOFError: + pass + tb.stop() + tb.wait() + + +if __name__ == '__main__': + main() diff --git a/examples/lora_quality_analyzer.grc b/examples/lora_quality_analyzer.grc new file mode 100644 index 0000000..b34650d --- /dev/null +++ b/examples/lora_quality_analyzer.grc @@ -0,0 +1,673 @@ +options: + parameters: + author: gr-mcp + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: LoRa decoder with IQ recording and real-time signal power measurement. + gen_cmake: 'On' + gen_linking: dynamic + generate_options: no_gui + hier_block_src_path: '.:' + id: lora_quality_analyzer + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: prompt + sizing_mode: fixed + thread_safe_setters: '' + title: LoRa Signal Quality Analyzer + window_size: (1000,1000) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: center_freq + id: variable + parameters: + comment: '' + value: 915e6 + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: iq_file + id: variable + parameters: + comment: '' + value: '"/tmp/iq_capture.cf32"' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_bw + id: variable + parameters: + comment: '' + value: '125000' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_sf + id: variable + parameters: + comment: '' + value: '7' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: recording_selector + id: variable + parameters: + comment: '' + value: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: rf_gain + id: variable + parameters: + comment: '' + value: '40' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: '1000000' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: signal_power_db + id: variable_function_probe + parameters: + block_id: blocks_probe_signal_x_0 + comment: '' + function_args: '' + function_name: level + poll_rate: '2' + value: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_complex_to_mag_squared_0 + id: blocks_complex_to_mag_squared + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_file_sink_0 + id: blocks_file_sink + parameters: + affinity: '' + alias: '' + append: 'False' + comment: '' + file: iq_file + type: complex + unbuffered: 'False' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_message_debug_0 + id: blocks_message_debug + parameters: + affinity: '' + alias: '' + comment: '' + en_uvec: 'True' + log_level: info + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_moving_average_xx_0 + id: blocks_moving_average_xx + parameters: + affinity: '' + alias: '' + comment: '' + length: '10000' + max_iter: '4000' + maxoutbuf: '0' + minoutbuf: '0' + scale: 1.0/10000 + type: float + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_nlog10_ff_0 + id: blocks_nlog10_ff + parameters: + affinity: '' + alias: '' + comment: '' + k: '0' + maxoutbuf: '0' + minoutbuf: '0' + n: '10' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_null_sink_0 + id: blocks_null_sink + parameters: + affinity: '' + alias: '' + bus_structure_sink: '[[0,],]' + comment: '' + num_inputs: '1' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_probe_signal_x_0 + id: blocks_probe_signal_x + parameters: + affinity: '' + alias: '' + comment: '' + type: float + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_selector_0 + id: blocks_selector + parameters: + affinity: '' + alias: '' + comment: '' + enabled: 'True' + input_index: '0' + maxoutbuf: '0' + minoutbuf: '0' + num_inputs: '1' + num_outputs: '2' + output_index: recording_selector + showports: 'True' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_rx_0 + id: lora_rx + parameters: + affinity: '' + alias: '' + bw: lora_bw + comment: '' + cr: '1' + has_crc: 'True' + impl_head: 'False' + ldro: '2' + maxoutbuf: '0' + minoutbuf: '0' + pay_len: '255' + print_rx: '[True,True]' + samp_rate: int(samp_rate/2) + sf: lora_sf + soft_decoding: 'True' + sync_word: '[0x12]' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: low_pass_filter_0 + id: low_pass_filter + parameters: + affinity: '' + alias: '' + beta: '6.76' + comment: '' + cutoff_freq: 200e3 + decim: '2' + gain: '1' + interp: '1' + maxoutbuf: '0' + minoutbuf: '0' + samp_rate: samp_rate + type: fir_filter_ccf + width: 50e3 + win: window.WIN_HAMMING + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: osmosdr_source_0 + id: osmosdr_source + parameters: + affinity: '' + alias: '' + ant0: '' + ant1: '' + ant10: '' + ant11: '' + ant12: '' + ant13: '' + ant14: '' + ant15: '' + ant16: '' + ant17: '' + ant18: '' + ant19: '' + ant2: '' + ant20: '' + ant21: '' + ant22: '' + ant23: '' + ant24: '' + ant25: '' + ant26: '' + ant27: '' + ant28: '' + ant29: '' + ant3: '' + ant30: '' + ant31: '' + ant4: '' + ant5: '' + ant6: '' + ant7: '' + ant8: '' + ant9: '' + args: rtl=0 + bb_gain0: '20' + bb_gain1: '20' + bb_gain10: '20' + bb_gain11: '20' + bb_gain12: '20' + bb_gain13: '20' + bb_gain14: '20' + bb_gain15: '20' + bb_gain16: '20' + bb_gain17: '20' + bb_gain18: '20' + bb_gain19: '20' + bb_gain2: '20' + bb_gain20: '20' + bb_gain21: '20' + bb_gain22: '20' + bb_gain23: '20' + bb_gain24: '20' + bb_gain25: '20' + bb_gain26: '20' + bb_gain27: '20' + bb_gain28: '20' + bb_gain29: '20' + bb_gain3: '20' + bb_gain30: '20' + bb_gain31: '20' + bb_gain4: '20' + bb_gain5: '20' + bb_gain6: '20' + bb_gain7: '20' + bb_gain8: '20' + bb_gain9: '20' + bw0: '0' + bw1: '0' + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' + comment: '' + corr0: '0' + corr1: '0' + corr10: '0' + corr11: '0' + corr12: '0' + corr13: '0' + corr14: '0' + corr15: '0' + corr16: '0' + corr17: '0' + corr18: '0' + corr19: '0' + corr2: '0' + corr20: '0' + corr21: '0' + corr22: '0' + corr23: '0' + corr24: '0' + corr25: '0' + corr26: '0' + corr27: '0' + corr28: '0' + corr29: '0' + corr3: '0' + corr30: '0' + corr31: '0' + corr4: '0' + corr5: '0' + corr6: '0' + corr7: '0' + corr8: '0' + corr9: '0' + dc_offset_mode0: '2' + dc_offset_mode1: '0' + dc_offset_mode10: '0' + dc_offset_mode11: '0' + dc_offset_mode12: '0' + dc_offset_mode13: '0' + dc_offset_mode14: '0' + dc_offset_mode15: '0' + dc_offset_mode16: '0' + dc_offset_mode17: '0' + dc_offset_mode18: '0' + dc_offset_mode19: '0' + dc_offset_mode2: '0' + dc_offset_mode20: '0' + dc_offset_mode21: '0' + dc_offset_mode22: '0' + dc_offset_mode23: '0' + dc_offset_mode24: '0' + dc_offset_mode25: '0' + dc_offset_mode26: '0' + dc_offset_mode27: '0' + dc_offset_mode28: '0' + dc_offset_mode29: '0' + dc_offset_mode3: '0' + dc_offset_mode30: '0' + dc_offset_mode31: '0' + dc_offset_mode4: '0' + dc_offset_mode5: '0' + dc_offset_mode6: '0' + dc_offset_mode7: '0' + dc_offset_mode8: '0' + dc_offset_mode9: '0' + freq0: center_freq + freq1: 100e6 + freq10: 100e6 + freq11: 100e6 + freq12: 100e6 + freq13: 100e6 + freq14: 100e6 + freq15: 100e6 + freq16: 100e6 + freq17: 100e6 + freq18: 100e6 + freq19: 100e6 + freq2: 100e6 + freq20: 100e6 + freq21: 100e6 + freq22: 100e6 + freq23: 100e6 + freq24: 100e6 + freq25: 100e6 + freq26: 100e6 + freq27: 100e6 + freq28: 100e6 + freq29: 100e6 + freq3: 100e6 + freq30: 100e6 + freq31: 100e6 + freq4: 100e6 + freq5: 100e6 + freq6: 100e6 + freq7: 100e6 + freq8: 100e6 + freq9: 100e6 + gain0: rf_gain + gain1: '10' + gain10: '10' + gain11: '10' + gain12: '10' + gain13: '10' + gain14: '10' + gain15: '10' + gain16: '10' + gain17: '10' + gain18: '10' + gain19: '10' + gain2: '10' + gain20: '10' + gain21: '10' + gain22: '10' + gain23: '10' + gain24: '10' + gain25: '10' + gain26: '10' + gain27: '10' + gain28: '10' + gain29: '10' + gain3: '10' + gain30: '10' + gain31: '10' + gain4: '10' + gain5: '10' + gain6: '10' + gain7: '10' + gain8: '10' + gain9: '10' + gain_mode0: 'False' + gain_mode1: 'False' + gain_mode10: 'False' + gain_mode11: 'False' + gain_mode12: 'False' + gain_mode13: 'False' + gain_mode14: 'False' + gain_mode15: 'False' + gain_mode16: 'False' + gain_mode17: 'False' + gain_mode18: 'False' + gain_mode19: 'False' + gain_mode2: 'False' + gain_mode20: 'False' + gain_mode21: 'False' + gain_mode22: 'False' + gain_mode23: 'False' + gain_mode24: 'False' + gain_mode25: 'False' + gain_mode26: 'False' + gain_mode27: 'False' + gain_mode28: 'False' + gain_mode29: 'False' + gain_mode3: 'False' + gain_mode30: 'False' + gain_mode31: 'False' + gain_mode4: 'False' + gain_mode5: 'False' + gain_mode6: 'False' + gain_mode7: 'False' + gain_mode8: 'False' + gain_mode9: 'False' + if_gain0: '20' + if_gain1: '20' + if_gain10: '20' + if_gain11: '20' + if_gain12: '20' + if_gain13: '20' + if_gain14: '20' + if_gain15: '20' + if_gain16: '20' + if_gain17: '20' + if_gain18: '20' + if_gain19: '20' + if_gain2: '20' + if_gain20: '20' + if_gain21: '20' + if_gain22: '20' + if_gain23: '20' + if_gain24: '20' + if_gain25: '20' + if_gain26: '20' + if_gain27: '20' + if_gain28: '20' + if_gain29: '20' + if_gain3: '20' + if_gain30: '20' + if_gain31: '20' + if_gain4: '20' + if_gain5: '20' + if_gain6: '20' + if_gain7: '20' + if_gain8: '20' + if_gain9: '20' + iq_balance_mode0: '2' + iq_balance_mode1: '0' + iq_balance_mode10: '0' + iq_balance_mode11: '0' + iq_balance_mode12: '0' + iq_balance_mode13: '0' + iq_balance_mode14: '0' + iq_balance_mode15: '0' + iq_balance_mode16: '0' + iq_balance_mode17: '0' + iq_balance_mode18: '0' + iq_balance_mode19: '0' + iq_balance_mode2: '0' + iq_balance_mode20: '0' + iq_balance_mode21: '0' + iq_balance_mode22: '0' + iq_balance_mode23: '0' + iq_balance_mode24: '0' + iq_balance_mode25: '0' + iq_balance_mode26: '0' + iq_balance_mode27: '0' + iq_balance_mode28: '0' + iq_balance_mode29: '0' + iq_balance_mode3: '0' + iq_balance_mode30: '0' + iq_balance_mode31: '0' + iq_balance_mode4: '0' + iq_balance_mode5: '0' + iq_balance_mode6: '0' + iq_balance_mode7: '0' + iq_balance_mode8: '0' + iq_balance_mode9: '0' + maxoutbuf: '0' + minoutbuf: '0' + nchan: '1' + num_mboards: '1' + sample_rate: samp_rate + sync: sync + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: xmlrpc_server_0 + id: xmlrpc_server + parameters: + addr: localhost + alias: '' + comment: '' + port: '8080' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled + +connections: +- [blocks_complex_to_mag_squared_0, '0', blocks_moving_average_xx_0, '0'] +- [blocks_moving_average_xx_0, '0', blocks_nlog10_ff_0, '0'] +- [blocks_nlog10_ff_0, '0', blocks_probe_signal_x_0, '0'] +- [blocks_selector_0, '0', blocks_file_sink_0, '0'] +- [blocks_selector_0, '1', blocks_null_sink_0, '0'] +- [lora_rx_0, out, blocks_message_debug_0, print] +- [low_pass_filter_0, '0', blocks_complex_to_mag_squared_0, '0'] +- [low_pass_filter_0, '0', blocks_selector_0, '0'] +- [low_pass_filter_0, '0', lora_rx_0, '0'] +- [osmosdr_source_0, '0', low_pass_filter_0, '0'] + +metadata: + file_format: 1 + grc_version: 3.10.12.0 diff --git a/examples/lora_quality_analyzer.py b/examples/lora_quality_analyzer.py new file mode 100755 index 0000000..12c6c59 --- /dev/null +++ b/examples/lora_quality_analyzer.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# +# SPDX-License-Identifier: GPL-3.0 +# +# GNU Radio Python Flow Graph +# Title: LoRa Signal Quality Analyzer +# Author: gr-mcp +# Description: LoRa decoder with IQ recording and real-time signal power measurement. +# GNU Radio version: 3.10.12.0 + +from gnuradio import blocks +from gnuradio import blocks, gr +from gnuradio import filter +from gnuradio.filter import firdes +from gnuradio import gr +from gnuradio.fft import window +import sys +import signal +from argparse import ArgumentParser +from gnuradio.eng_arg import eng_float, intx +from gnuradio import eng_notation +from xmlrpc.server import SimpleXMLRPCServer +import threading +import gnuradio.lora_sdr as lora_sdr +import osmosdr +import time + + + + +class lora_quality_analyzer(gr.top_block): + + def __init__(self): + gr.top_block.__init__(self, "LoRa Signal Quality Analyzer", catch_exceptions=True) + self.flowgraph_started = threading.Event() + + ################################################## + # Variables + ################################################## + self.signal_power_db = signal_power_db = 0 + self.samp_rate = samp_rate = 1000000 + self.rf_gain = rf_gain = 40 + self.recording_selector = recording_selector = 1 + self.lora_sf = lora_sf = 7 + self.lora_bw = lora_bw = 125000 + self.iq_file = iq_file = "/tmp/iq_capture.cf32" + self.center_freq = center_freq = 915e6 + + ################################################## + # Blocks + ################################################## + + self.blocks_probe_signal_x_0 = blocks.probe_signal_f() + self.xmlrpc_server_0 = SimpleXMLRPCServer(('localhost', 8080), allow_none=True) + self.xmlrpc_server_0.register_instance(self) + self.xmlrpc_server_0_thread = threading.Thread(target=self.xmlrpc_server_0.serve_forever) + self.xmlrpc_server_0_thread.daemon = True + self.xmlrpc_server_0_thread.start() + def _signal_power_db_probe(): + self.flowgraph_started.wait() + while True: + + val = self.blocks_probe_signal_x_0.level() + try: + try: + self.doc.add_next_tick_callback(functools.partial(self.set_signal_power_db,val)) + except AttributeError: + self.set_signal_power_db(val) + except AttributeError: + pass + time.sleep(1.0 / (2)) + _signal_power_db_thread = threading.Thread(target=_signal_power_db_probe) + _signal_power_db_thread.daemon = True + _signal_power_db_thread.start() + self.osmosdr_source_0 = osmosdr.source( + args="numchan=" + str(1) + " " + 'rtl=0' + ) + self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t()) + self.osmosdr_source_0.set_sample_rate(samp_rate) + self.osmosdr_source_0.set_center_freq(center_freq, 0) + self.osmosdr_source_0.set_freq_corr(0, 0) + self.osmosdr_source_0.set_dc_offset_mode(2, 0) + self.osmosdr_source_0.set_iq_balance_mode(2, 0) + self.osmosdr_source_0.set_gain_mode(False, 0) + self.osmosdr_source_0.set_gain(rf_gain, 0) + self.osmosdr_source_0.set_if_gain(20, 0) + self.osmosdr_source_0.set_bb_gain(20, 0) + self.osmosdr_source_0.set_antenna('', 0) + self.osmosdr_source_0.set_bandwidth(0, 0) + self.low_pass_filter_0 = filter.fir_filter_ccf( + 2, + firdes.low_pass( + 1, + samp_rate, + 200e3, + 50e3, + window.WIN_HAMMING, + 6.76)) + self.lora_rx_0 = lora_sdr.lora_sdr_lora_rx( bw=lora_bw, cr=1, has_crc=True, impl_head=False, pay_len=255, samp_rate=(int(samp_rate/2)), sf=lora_sf, sync_word=[0x12], soft_decoding=True, ldro_mode=2, print_rx=[True,True]) + self.blocks_selector_0 = blocks.selector(gr.sizeof_gr_complex*1,0,recording_selector) + self.blocks_selector_0.set_enabled(True) + self.blocks_null_sink_0 = blocks.null_sink(gr.sizeof_gr_complex*1) + self.blocks_nlog10_ff_0 = blocks.nlog10_ff(10, 1, 0) + self.blocks_moving_average_xx_0 = blocks.moving_average_ff(10000, (1.0/10000), 4000, 1) + self.blocks_message_debug_0 = blocks.message_debug(True, gr.log_levels.info) + self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_gr_complex*1, iq_file, False) + self.blocks_file_sink_0.set_unbuffered(False) + self.blocks_complex_to_mag_squared_0 = blocks.complex_to_mag_squared(1) + + + ################################################## + # Connections + ################################################## + self.msg_connect((self.lora_rx_0, 'out'), (self.blocks_message_debug_0, 'print')) + self.connect((self.blocks_complex_to_mag_squared_0, 0), (self.blocks_moving_average_xx_0, 0)) + self.connect((self.blocks_moving_average_xx_0, 0), (self.blocks_nlog10_ff_0, 0)) + self.connect((self.blocks_nlog10_ff_0, 0), (self.blocks_probe_signal_x_0, 0)) + self.connect((self.blocks_selector_0, 0), (self.blocks_file_sink_0, 0)) + self.connect((self.blocks_selector_0, 1), (self.blocks_null_sink_0, 0)) + self.connect((self.low_pass_filter_0, 0), (self.blocks_complex_to_mag_squared_0, 0)) + self.connect((self.low_pass_filter_0, 0), (self.blocks_selector_0, 0)) + self.connect((self.low_pass_filter_0, 0), (self.lora_rx_0, 0)) + self.connect((self.osmosdr_source_0, 0), (self.low_pass_filter_0, 0)) + + + def get_signal_power_db(self): + return self.signal_power_db + + def set_signal_power_db(self, signal_power_db): + self.signal_power_db = signal_power_db + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + self.low_pass_filter_0.set_taps(firdes.low_pass(1, self.samp_rate, 200e3, 50e3, window.WIN_HAMMING, 6.76)) + self.osmosdr_source_0.set_sample_rate(self.samp_rate) + + def get_rf_gain(self): + return self.rf_gain + + def set_rf_gain(self, rf_gain): + self.rf_gain = rf_gain + self.osmosdr_source_0.set_gain(self.rf_gain, 0) + + def get_recording_selector(self): + return self.recording_selector + + def set_recording_selector(self, recording_selector): + self.recording_selector = recording_selector + self.blocks_selector_0.set_output_index(self.recording_selector) + + def get_lora_sf(self): + return self.lora_sf + + def set_lora_sf(self, lora_sf): + self.lora_sf = lora_sf + + def get_lora_bw(self): + return self.lora_bw + + def set_lora_bw(self, lora_bw): + self.lora_bw = lora_bw + + def get_iq_file(self): + return self.iq_file + + def set_iq_file(self, iq_file): + self.iq_file = iq_file + self.blocks_file_sink_0.open(self.iq_file) + + def get_center_freq(self): + return self.center_freq + + def set_center_freq(self, center_freq): + self.center_freq = center_freq + self.osmosdr_source_0.set_center_freq(self.center_freq, 0) + + + + +def main(top_block_cls=lora_quality_analyzer, options=None): + tb = top_block_cls() + + def sig_handler(sig=None, frame=None): + tb.stop() + tb.wait() + + sys.exit(0) + + signal.signal(signal.SIGINT, sig_handler) + signal.signal(signal.SIGTERM, sig_handler) + + tb.start() + tb.flowgraph_started.set() + + try: + input('Press Enter to quit: ') + except EOFError: + pass + tb.stop() + tb.wait() + + +if __name__ == '__main__': + main() diff --git a/examples/multi_sf_lora_rx.grc b/examples/multi_sf_lora_rx.grc new file mode 100644 index 0000000..6d736f8 --- /dev/null +++ b/examples/multi_sf_lora_rx.grc @@ -0,0 +1,588 @@ +options: + parameters: + author: gr-mcp + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: Simultaneous LoRa decoder for SF7-SF12. 915MHz, BW125k. + gen_cmake: 'On' + gen_linking: dynamic + generate_options: no_gui + hier_block_src_path: '.:' + id: multi_sf_lora_rx + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: prompt + sizing_mode: fixed + thread_safe_setters: '' + title: Multi-SF LoRa Receiver + window_size: (1000,1000) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: center_freq + id: variable + parameters: + comment: '' + value: 915e6 + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_bw + id: variable + parameters: + comment: '' + value: '125000' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: rf_gain + id: variable + parameters: + comment: '' + value: '40' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: '1000000' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: blocks_message_debug_0 + id: blocks_message_debug + parameters: + affinity: '' + alias: '' + comment: '' + en_uvec: 'True' + log_level: info + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_rx_0 + id: lora_rx + parameters: + affinity: '' + alias: '' + bw: lora_bw + comment: '' + cr: '1' + has_crc: 'True' + impl_head: 'False' + ldro: '2' + maxoutbuf: '0' + minoutbuf: '0' + pay_len: '255' + print_rx: '[True,True]' + samp_rate: int(samp_rate/2) + sf: '7' + soft_decoding: 'True' + sync_word: '[0x12]' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_rx_1 + id: lora_rx + parameters: + affinity: '' + alias: '' + bw: lora_bw + comment: '' + cr: '1' + has_crc: 'True' + impl_head: 'False' + ldro: '2' + maxoutbuf: '0' + minoutbuf: '0' + pay_len: '255' + print_rx: '[True,True]' + samp_rate: int(samp_rate/2) + sf: '8' + soft_decoding: 'True' + sync_word: '[0x12]' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_rx_2 + id: lora_rx + parameters: + affinity: '' + alias: '' + bw: lora_bw + comment: '' + cr: '1' + has_crc: 'True' + impl_head: 'False' + ldro: '2' + maxoutbuf: '0' + minoutbuf: '0' + pay_len: '255' + print_rx: '[True,True]' + samp_rate: int(samp_rate/2) + sf: '9' + soft_decoding: 'True' + sync_word: '[0x12]' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: lora_rx_3 + id: lora_rx + parameters: + affinity: '' + alias: '' + bw: lora_bw + comment: '' + cr: '1' + has_crc: 'True' + impl_head: 'False' + ldro: '2' + maxoutbuf: '0' + minoutbuf: '0' + pay_len: '255' + print_rx: '[True,True]' + samp_rate: int(samp_rate/2) + sf: '10' + soft_decoding: 'True' + sync_word: '[0x12]' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: low_pass_filter_0 + id: low_pass_filter + parameters: + affinity: '' + alias: '' + beta: '6.76' + comment: '' + cutoff_freq: 200e3 + decim: '2' + gain: '1' + interp: '1' + maxoutbuf: '0' + minoutbuf: '0' + samp_rate: samp_rate + type: fir_filter_ccf + width: 50e3 + win: window.WIN_HAMMING + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: osmosdr_source_0 + id: osmosdr_source + parameters: + affinity: '' + alias: '' + ant0: '' + ant1: '' + ant10: '' + ant11: '' + ant12: '' + ant13: '' + ant14: '' + ant15: '' + ant16: '' + ant17: '' + ant18: '' + ant19: '' + ant2: '' + ant20: '' + ant21: '' + ant22: '' + ant23: '' + ant24: '' + ant25: '' + ant26: '' + ant27: '' + ant28: '' + ant29: '' + ant3: '' + ant30: '' + ant31: '' + ant4: '' + ant5: '' + ant6: '' + ant7: '' + ant8: '' + ant9: '' + args: rtl=0 + bb_gain0: '20' + bb_gain1: '20' + bb_gain10: '20' + bb_gain11: '20' + bb_gain12: '20' + bb_gain13: '20' + bb_gain14: '20' + bb_gain15: '20' + bb_gain16: '20' + bb_gain17: '20' + bb_gain18: '20' + bb_gain19: '20' + bb_gain2: '20' + bb_gain20: '20' + bb_gain21: '20' + bb_gain22: '20' + bb_gain23: '20' + bb_gain24: '20' + bb_gain25: '20' + bb_gain26: '20' + bb_gain27: '20' + bb_gain28: '20' + bb_gain29: '20' + bb_gain3: '20' + bb_gain30: '20' + bb_gain31: '20' + bb_gain4: '20' + bb_gain5: '20' + bb_gain6: '20' + bb_gain7: '20' + bb_gain8: '20' + bb_gain9: '20' + bw0: '0' + bw1: '0' + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' + comment: '' + corr0: '0' + corr1: '0' + corr10: '0' + corr11: '0' + corr12: '0' + corr13: '0' + corr14: '0' + corr15: '0' + corr16: '0' + corr17: '0' + corr18: '0' + corr19: '0' + corr2: '0' + corr20: '0' + corr21: '0' + corr22: '0' + corr23: '0' + corr24: '0' + corr25: '0' + corr26: '0' + corr27: '0' + corr28: '0' + corr29: '0' + corr3: '0' + corr30: '0' + corr31: '0' + corr4: '0' + corr5: '0' + corr6: '0' + corr7: '0' + corr8: '0' + corr9: '0' + dc_offset_mode0: '2' + dc_offset_mode1: '0' + dc_offset_mode10: '0' + dc_offset_mode11: '0' + dc_offset_mode12: '0' + dc_offset_mode13: '0' + dc_offset_mode14: '0' + dc_offset_mode15: '0' + dc_offset_mode16: '0' + dc_offset_mode17: '0' + dc_offset_mode18: '0' + dc_offset_mode19: '0' + dc_offset_mode2: '0' + dc_offset_mode20: '0' + dc_offset_mode21: '0' + dc_offset_mode22: '0' + dc_offset_mode23: '0' + dc_offset_mode24: '0' + dc_offset_mode25: '0' + dc_offset_mode26: '0' + dc_offset_mode27: '0' + dc_offset_mode28: '0' + dc_offset_mode29: '0' + dc_offset_mode3: '0' + dc_offset_mode30: '0' + dc_offset_mode31: '0' + dc_offset_mode4: '0' + dc_offset_mode5: '0' + dc_offset_mode6: '0' + dc_offset_mode7: '0' + dc_offset_mode8: '0' + dc_offset_mode9: '0' + freq0: center_freq + freq1: 100e6 + freq10: 100e6 + freq11: 100e6 + freq12: 100e6 + freq13: 100e6 + freq14: 100e6 + freq15: 100e6 + freq16: 100e6 + freq17: 100e6 + freq18: 100e6 + freq19: 100e6 + freq2: 100e6 + freq20: 100e6 + freq21: 100e6 + freq22: 100e6 + freq23: 100e6 + freq24: 100e6 + freq25: 100e6 + freq26: 100e6 + freq27: 100e6 + freq28: 100e6 + freq29: 100e6 + freq3: 100e6 + freq30: 100e6 + freq31: 100e6 + freq4: 100e6 + freq5: 100e6 + freq6: 100e6 + freq7: 100e6 + freq8: 100e6 + freq9: 100e6 + gain0: rf_gain + gain1: '10' + gain10: '10' + gain11: '10' + gain12: '10' + gain13: '10' + gain14: '10' + gain15: '10' + gain16: '10' + gain17: '10' + gain18: '10' + gain19: '10' + gain2: '10' + gain20: '10' + gain21: '10' + gain22: '10' + gain23: '10' + gain24: '10' + gain25: '10' + gain26: '10' + gain27: '10' + gain28: '10' + gain29: '10' + gain3: '10' + gain30: '10' + gain31: '10' + gain4: '10' + gain5: '10' + gain6: '10' + gain7: '10' + gain8: '10' + gain9: '10' + gain_mode0: 'False' + gain_mode1: 'False' + gain_mode10: 'False' + gain_mode11: 'False' + gain_mode12: 'False' + gain_mode13: 'False' + gain_mode14: 'False' + gain_mode15: 'False' + gain_mode16: 'False' + gain_mode17: 'False' + gain_mode18: 'False' + gain_mode19: 'False' + gain_mode2: 'False' + gain_mode20: 'False' + gain_mode21: 'False' + gain_mode22: 'False' + gain_mode23: 'False' + gain_mode24: 'False' + gain_mode25: 'False' + gain_mode26: 'False' + gain_mode27: 'False' + gain_mode28: 'False' + gain_mode29: 'False' + gain_mode3: 'False' + gain_mode30: 'False' + gain_mode31: 'False' + gain_mode4: 'False' + gain_mode5: 'False' + gain_mode6: 'False' + gain_mode7: 'False' + gain_mode8: 'False' + gain_mode9: 'False' + if_gain0: '20' + if_gain1: '20' + if_gain10: '20' + if_gain11: '20' + if_gain12: '20' + if_gain13: '20' + if_gain14: '20' + if_gain15: '20' + if_gain16: '20' + if_gain17: '20' + if_gain18: '20' + if_gain19: '20' + if_gain2: '20' + if_gain20: '20' + if_gain21: '20' + if_gain22: '20' + if_gain23: '20' + if_gain24: '20' + if_gain25: '20' + if_gain26: '20' + if_gain27: '20' + if_gain28: '20' + if_gain29: '20' + if_gain3: '20' + if_gain30: '20' + if_gain31: '20' + if_gain4: '20' + if_gain5: '20' + if_gain6: '20' + if_gain7: '20' + if_gain8: '20' + if_gain9: '20' + iq_balance_mode0: '2' + iq_balance_mode1: '0' + iq_balance_mode10: '0' + iq_balance_mode11: '0' + iq_balance_mode12: '0' + iq_balance_mode13: '0' + iq_balance_mode14: '0' + iq_balance_mode15: '0' + iq_balance_mode16: '0' + iq_balance_mode17: '0' + iq_balance_mode18: '0' + iq_balance_mode19: '0' + iq_balance_mode2: '0' + iq_balance_mode20: '0' + iq_balance_mode21: '0' + iq_balance_mode22: '0' + iq_balance_mode23: '0' + iq_balance_mode24: '0' + iq_balance_mode25: '0' + iq_balance_mode26: '0' + iq_balance_mode27: '0' + iq_balance_mode28: '0' + iq_balance_mode29: '0' + iq_balance_mode3: '0' + iq_balance_mode30: '0' + iq_balance_mode31: '0' + iq_balance_mode4: '0' + iq_balance_mode5: '0' + iq_balance_mode6: '0' + iq_balance_mode7: '0' + iq_balance_mode8: '0' + iq_balance_mode9: '0' + maxoutbuf: '0' + minoutbuf: '0' + nchan: '1' + num_mboards: '1' + sample_rate: samp_rate + sync: sync + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled +- name: xmlrpc_server_0 + id: xmlrpc_server + parameters: + addr: localhost + alias: '' + comment: '' + port: '8080' + states: + bus_sink: false + bus_source: false + bus_structure: null + state: enabled + +connections: +- [lora_rx_0, out, blocks_message_debug_0, print] +- [lora_rx_1, out, blocks_message_debug_0, print] +- [lora_rx_2, out, blocks_message_debug_0, print] +- [lora_rx_3, out, blocks_message_debug_0, print] +- [low_pass_filter_0, '0', lora_rx_0, '0'] +- [low_pass_filter_0, '0', lora_rx_1, '0'] +- [low_pass_filter_0, '0', lora_rx_2, '0'] +- [low_pass_filter_0, '0', lora_rx_3, '0'] +- [osmosdr_source_0, '0', low_pass_filter_0, '0'] + +metadata: + file_format: 1 + grc_version: 3.10.12.0 diff --git a/examples/multi_sf_lora_rx.py b/examples/multi_sf_lora_rx.py new file mode 100755 index 0000000..bf230a6 --- /dev/null +++ b/examples/multi_sf_lora_rx.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# +# SPDX-License-Identifier: GPL-3.0 +# +# GNU Radio Python Flow Graph +# Title: Multi-SF LoRa Receiver +# Author: gr-mcp +# Description: Simultaneous LoRa decoder for SF7-SF12. 915MHz, BW125k. +# GNU Radio version: 3.10.12.0 + +from gnuradio import blocks, gr +from gnuradio import filter +from gnuradio.filter import firdes +from gnuradio import gr +from gnuradio.fft import window +import sys +import signal +from argparse import ArgumentParser +from gnuradio.eng_arg import eng_float, intx +from gnuradio import eng_notation +from xmlrpc.server import SimpleXMLRPCServer +import threading +import gnuradio.lora_sdr as lora_sdr +import osmosdr +import time + + + + +class multi_sf_lora_rx(gr.top_block): + + def __init__(self): + gr.top_block.__init__(self, "Multi-SF LoRa Receiver", catch_exceptions=True) + self.flowgraph_started = threading.Event() + + ################################################## + # Variables + ################################################## + self.samp_rate = samp_rate = 1000000 + self.rf_gain = rf_gain = 40 + self.lora_bw = lora_bw = 125000 + self.center_freq = center_freq = 915e6 + + ################################################## + # Blocks + ################################################## + + self.xmlrpc_server_0 = SimpleXMLRPCServer(('localhost', 8080), allow_none=True) + self.xmlrpc_server_0.register_instance(self) + self.xmlrpc_server_0_thread = threading.Thread(target=self.xmlrpc_server_0.serve_forever) + self.xmlrpc_server_0_thread.daemon = True + self.xmlrpc_server_0_thread.start() + self.osmosdr_source_0 = osmosdr.source( + args="numchan=" + str(1) + " " + 'rtl=0' + ) + self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t()) + self.osmosdr_source_0.set_sample_rate(samp_rate) + self.osmosdr_source_0.set_center_freq(center_freq, 0) + self.osmosdr_source_0.set_freq_corr(0, 0) + self.osmosdr_source_0.set_dc_offset_mode(2, 0) + self.osmosdr_source_0.set_iq_balance_mode(2, 0) + self.osmosdr_source_0.set_gain_mode(False, 0) + self.osmosdr_source_0.set_gain(rf_gain, 0) + self.osmosdr_source_0.set_if_gain(20, 0) + self.osmosdr_source_0.set_bb_gain(20, 0) + self.osmosdr_source_0.set_antenna('', 0) + self.osmosdr_source_0.set_bandwidth(0, 0) + self.low_pass_filter_0 = filter.fir_filter_ccf( + 2, + firdes.low_pass( + 1, + samp_rate, + 200e3, + 50e3, + window.WIN_HAMMING, + 6.76)) + self.lora_rx_3 = lora_sdr.lora_sdr_lora_rx( bw=lora_bw, cr=1, has_crc=True, impl_head=False, pay_len=255, samp_rate=(int(samp_rate/2)), sf=10, sync_word=[0x12], soft_decoding=True, ldro_mode=2, print_rx=[True,True]) + self.lora_rx_2 = lora_sdr.lora_sdr_lora_rx( bw=lora_bw, cr=1, has_crc=True, impl_head=False, pay_len=255, samp_rate=(int(samp_rate/2)), sf=9, sync_word=[0x12], soft_decoding=True, ldro_mode=2, print_rx=[True,True]) + self.lora_rx_1 = lora_sdr.lora_sdr_lora_rx( bw=lora_bw, cr=1, has_crc=True, impl_head=False, pay_len=255, samp_rate=(int(samp_rate/2)), sf=8, sync_word=[0x12], soft_decoding=True, ldro_mode=2, print_rx=[True,True]) + self.lora_rx_0 = lora_sdr.lora_sdr_lora_rx( bw=lora_bw, cr=1, has_crc=True, impl_head=False, pay_len=255, samp_rate=(int(samp_rate/2)), sf=7, sync_word=[0x12], soft_decoding=True, ldro_mode=2, print_rx=[True,True]) + self.blocks_message_debug_0 = blocks.message_debug(True, gr.log_levels.info) + + + ################################################## + # Connections + ################################################## + self.msg_connect((self.lora_rx_0, 'out'), (self.blocks_message_debug_0, 'print')) + self.msg_connect((self.lora_rx_1, 'out'), (self.blocks_message_debug_0, 'print')) + self.msg_connect((self.lora_rx_2, 'out'), (self.blocks_message_debug_0, 'print')) + self.msg_connect((self.lora_rx_3, 'out'), (self.blocks_message_debug_0, 'print')) + self.connect((self.low_pass_filter_0, 0), (self.lora_rx_0, 0)) + self.connect((self.low_pass_filter_0, 0), (self.lora_rx_1, 0)) + self.connect((self.low_pass_filter_0, 0), (self.lora_rx_2, 0)) + self.connect((self.low_pass_filter_0, 0), (self.lora_rx_3, 0)) + self.connect((self.osmosdr_source_0, 0), (self.low_pass_filter_0, 0)) + + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + self.low_pass_filter_0.set_taps(firdes.low_pass(1, self.samp_rate, 200e3, 50e3, window.WIN_HAMMING, 6.76)) + self.osmosdr_source_0.set_sample_rate(self.samp_rate) + + def get_rf_gain(self): + return self.rf_gain + + def set_rf_gain(self, rf_gain): + self.rf_gain = rf_gain + self.osmosdr_source_0.set_gain(self.rf_gain, 0) + + def get_lora_bw(self): + return self.lora_bw + + def set_lora_bw(self, lora_bw): + self.lora_bw = lora_bw + + def get_center_freq(self): + return self.center_freq + + def set_center_freq(self, center_freq): + self.center_freq = center_freq + self.osmosdr_source_0.set_center_freq(self.center_freq, 0) + + + + +def main(top_block_cls=multi_sf_lora_rx, options=None): + tb = top_block_cls() + + def sig_handler(sig=None, frame=None): + tb.stop() + tb.wait() + + sys.exit(0) + + signal.signal(signal.SIGINT, sig_handler) + signal.signal(signal.SIGTERM, sig_handler) + + tb.start() + tb.flowgraph_started.set() + + try: + input('Press Enter to quit: ') + except EOFError: + pass + tb.stop() + tb.wait() + + +if __name__ == '__main__': + main() diff --git a/uv.lock b/uv.lock index 3c7c6bc..658d7cd 100644 --- a/uv.lock +++ b/uv.lock @@ -352,7 +352,7 @@ wheels = [ [[package]] name = "gnuradio-mcp" -version = "0.2.0" +version = "2026.2.20" source = { editable = "." } dependencies = [ { name = "fastmcp" },