diff --git a/astro.config.mjs b/astro.config.mjs index 8b9c894..4e78e51 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,6 +1,7 @@ import { defineConfig } from 'astro/config' import starlight from '@astrojs/starlight' import tailwindcss from '@tailwindcss/vite' +import starlightVersions from 'starlight-versions' // SEE: https://developers.google.com/analytics/devguides/collection/gtagjs const gaTrackingID = import.meta.env.GA_TRACKING_ID @@ -107,6 +108,12 @@ head.push( export default defineConfig({ integrations: [ starlight({ + plugins: [ + starlightVersions({ + current: { label: 'v2.0-alpha' }, + versions: [{ slug: '1.0', label: 'v1.0.5' }], + }), + ], title: 'grlx Docs', lastUpdated: true, description: @@ -126,10 +133,22 @@ export default defineConfig({ head, logo: { src: './src/assets/grlx.webp' }, social: [ - { icon: 'github', label: 'GitHub', href: 'https://github.com/gogrlx/grlx' }, + { + icon: 'github', + label: 'GitHub', + href: 'https://github.com/gogrlx/grlx', + }, { icon: 'x.com', label: 'X', href: 'https://x.com/gogrlx' }, - { icon: 'discord', label: 'Discord', href: 'https://discord.gg/RNsZ3KWjXm' }, - { icon: 'email', label: 'Email', href: 'mailto:grlx@adatomic.com?subject=Question' }, + { + icon: 'discord', + label: 'Discord', + href: 'https://discord.gg/RNsZ3KWjXm', + }, + { + icon: 'email', + label: 'Email', + href: 'mailto:grlx@adatomic.com?subject=Question', + }, ], sidebar: [ { label: 'Getting Started', link: '/getting-started' }, @@ -137,6 +156,8 @@ export default defineConfig({ label: 'Architecture', autogenerate: { directory: 'architecture' }, }, + { label: 'Cohorts', link: '/cohorts' }, + { label: 'Managing Sprouts', link: '/sprouts' }, { label: 'Ingredients', autogenerate: { directory: 'ingredients' }, @@ -145,6 +166,14 @@ export default defineConfig({ label: 'Recipes', autogenerate: { directory: 'recipes' }, }, + { label: 'SSH', link: '/ssh' }, + { + label: 'Security', + items: [ + { label: 'RBAC', link: '/rbac' }, + { label: 'Audit Logging', link: '/audit' }, + ], + }, { label: 'Glossary', link: '/glossary' }, ], }), diff --git a/bun.lock b/bun.lock index 47f8d46..b218864 100644 --- a/bun.lock +++ b/bun.lock @@ -5,20 +5,21 @@ "": { "name": "docs.grlx.dev", "dependencies": { - "@astrojs/starlight": "^0.37.6", - "@astrojs/starlight-tailwind": "^4.0.2", + "@astrojs/starlight": "^0.38.1", + "@astrojs/starlight-tailwind": "^5.0.0", "@fontsource/ibm-plex-mono": "^5.2.7", "@fontsource/ibm-plex-sans": "^5.2.8", "@fontsource/ibm-plex-serif": "^5.2.7", "@tailwindcss/vite": "^4.2.1", - "astro": "^5.18.0", + "astro": "^6.0.4", "sharp": "^0.34.5", + "starlight-versions": "^0.8.1", "tailwindcss": "^4.2.1", }, "devDependencies": { - "@typescript-eslint/parser": "^8.56.1", + "@typescript-eslint/parser": "^8.57.0", "astro-eslint-parser": "^1.3.0", - "eslint": "^9.39.3", + "eslint": "^10.0.3", "eslint-plugin-astro": "^1.6.0", "prettier": "^3.8.1", "prettier-plugin-astro": "^0.14.1", @@ -30,19 +31,19 @@ "packages": { "@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="], - "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.5", "", {}, "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA=="], + "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.8.0", "", { "dependencies": { "picomatch": "^4.0.3" } }, "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w=="], - "@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.10", "", { "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" } }, "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A=="], + "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.0.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/prism": "4.0.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-jTAXHPy45L7o1ljH4jYV+ShtOHtyQUa1mGp3a5fJp1soX8lInuTJQ6ihmldHzVM4Q7QptU4SzIDIcKbBJO7sXQ=="], - "@astrojs/mdx": ["@astrojs/mdx@4.3.13", "", { "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" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q=="], + "@astrojs/mdx": ["@astrojs/mdx@5.0.0", "", { "dependencies": { "@astrojs/markdown-remark": "7.0.0", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.16.0", "es-module-lexer": "^2.0.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^6.0.0-alpha.0" } }, "sha512-J4rW6eT+qgVw7+RXdBYO4vYyWGeXXQp8wop9dXsOlLzIsVSxyttMCgkGCWvIR2ogBqKqeYgI6YDW93PaDHkCaA=="], - "@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], + "@astrojs/prism": ["@astrojs/prism@4.0.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-NndtNPpxaGinRpRytljGBvYHpTOwHycSZ/c+lQi5cHvkqqrHKWdkPEhImlODBNmbuB+vyQUNUDXyjzt66CihJg=="], - "@astrojs/sitemap": ["@astrojs/sitemap@3.7.0", "", { "dependencies": { "sitemap": "^8.0.2", "stream-replace-string": "^2.0.0", "zod": "^3.25.76" } }, "sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA=="], + "@astrojs/sitemap": ["@astrojs/sitemap@3.7.1", "", { "dependencies": { "sitemap": "^9.0.0", "stream-replace-string": "^2.0.0", "zod": "^4.3.6" } }, "sha512-IzQqdTeskaMX+QDZCzMuJIp8A8C1vgzMBp/NmHNnadepHYNHcxQdGLQZYfkbd2EbRXUfOS+UDIKx8sKg0oWVdw=="], - "@astrojs/starlight": ["@astrojs/starlight@0.37.6", "", { "dependencies": { "@astrojs/markdown-remark": "^6.3.1", "@astrojs/mdx": "^4.2.3", "@astrojs/sitemap": "^3.3.0", "@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.41.1", "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", "magic-string": "^0.30.17", "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", "ultrahtml": "^1.6.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "vfile": "^6.0.2" }, "peerDependencies": { "astro": "^5.5.0" } }, "sha512-wQrKwH431q+8FsLBnNQeG+R36TMtEGxTQ2AuiVpcx9APcazvL3n7wVW8mMmYyxX0POjTnxlcWPkdMGR3Yj1L+w=="], + "@astrojs/starlight": ["@astrojs/starlight@0.38.1", "", { "dependencies": { "@astrojs/markdown-remark": "^7.0.0", "@astrojs/mdx": "^5.0.0", "@astrojs/sitemap": "^3.7.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.41.6", "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", "magic-string": "^0.30.17", "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", "ultrahtml": "^1.6.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "vfile": "^6.0.2" }, "peerDependencies": { "astro": "^6.0.0" } }, "sha512-CATPH4Dy44OYAJhoyUHh6NqpColWEVufanGVwnM0l/bcaNMo5V/rypwL0Vu0Edp+ZIXE7/1DA9CrNj5jmCVSLQ=="], - "@astrojs/starlight-tailwind": ["@astrojs/starlight-tailwind@4.0.2", "", { "peerDependencies": { "@astrojs/starlight": ">=0.34.0", "tailwindcss": "^4.0.0" } }, "sha512-SYN/6zq6hJO5tWqbQ2tWT9/jd8ubUkzkBCcF94vByC/ZJ20Mi5GPjFvAh89Yky/aIM+jXxT6W5q4p6l58GKHiQ=="], + "@astrojs/starlight-tailwind": ["@astrojs/starlight-tailwind@5.0.0", "", { "peerDependencies": { "@astrojs/starlight": ">=0.38.0", "tailwindcss": "^4.0.0" } }, "sha512-VivF+bWg++4ma/ffr5sgHsd/ONtGdVJIKAaRZ6jmL4yqxy7bviu59MGNi5aW3nd8psP9i/aivBTrpwGxRM1XyA=="], "@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "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" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="], @@ -58,6 +59,10 @@ "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="], + "@clack/core": ["@clack/core@1.1.0", "", { "dependencies": { "sisteransi": "^1.0.5" } }, "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA=="], + + "@clack/prompts": ["@clack/prompts@1.1.0", "", { "dependencies": { "@clack/core": "1.1.0", "sisteransi": "^1.0.5" } }, "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g=="], + "@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.0", "", {}, "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A=="], "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], @@ -118,19 +123,15 @@ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], + "@eslint/config-array": ["@eslint/config-array@0.23.3", "", { "dependencies": { "@eslint/object-schema": "^3.0.3", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.5.3", "", { "dependencies": { "@eslint/core": "^1.1.1" } }, "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw=="], - "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + "@eslint/core": ["@eslint/core@1.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ=="], - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], + "@eslint/object-schema": ["@eslint/object-schema@3.0.3", "", {}, "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ=="], - "@eslint/js": ["@eslint/js@9.39.3", "", {}, "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.1", "", { "dependencies": { "@eslint/core": "^1.1.1", "levn": "^0.4.1" } }, "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ=="], "@expressive-code/core": ["@expressive-code/core@0.41.6", "", { "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" } }, "sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g=="], @@ -292,17 +293,19 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], - "@shikijs/core": ["@shikijs/core@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA=="], + "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw=="], + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], - "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA=="], + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="], - "@shikijs/langs": ["@shikijs/langs@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0" } }, "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA=="], + "@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="], - "@shikijs/themes": ["@shikijs/themes@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0" } }, "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g=="], + "@shikijs/primitive": ["@shikijs/primitive@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw=="], - "@shikijs/types": ["@shikijs/types@3.22.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg=="], + "@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="], + + "@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], @@ -338,6 +341,8 @@ "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -362,19 +367,19 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.1", "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.0", "@typescript-eslint/types": "^8.57.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1" } }, "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0" } }, "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.57.0", "", {}, "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.0", "@typescript-eslint/tsconfig-utils": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -384,12 +389,6 @@ "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], - "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], - - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], @@ -402,7 +401,7 @@ "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], - "astro": ["astro@5.18.0", "", { "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.27.3", "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" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-CHiohwJIS4L0G6/IzE1Fx3dgWqXBCXus/od0eGUfxrZJD2um2pE7ehclMmgL/fXqbU7NfE1Ze2pq34h2QaA6iQ=="], + "astro": ["astro@6.0.4", "", { "dependencies": { "@astrojs/compiler": "^3.0.0", "@astrojs/internal-helpers": "0.8.0", "@astrojs/markdown-remark": "7.0.0", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyclip": "^0.1.6", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^7.3.1", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "bin/astro.mjs" } }, "sha512-1piLJCPTL/x7AMO2cjVFSTFyRqKuC3W8sSEySCt1aJio+p/wGs5H3K+Xr/rE9ftKtknLUtjxCqCE7/0NsXfGpQ=="], "astro-eslint-parser": ["astro-eslint-parser@1.3.0", "", { "dependencies": { "@astrojs/compiler": "^2.0.0", "@typescript-eslint/scope-manager": "^7.0.0 || ^8.0.0", "@typescript-eslint/types": "^7.0.0 || ^8.0.0", "astrojs-compiler-sync": "^1.0.0", "debug": "^4.3.4", "entities": "^6.0.0", "eslint-scope": "^8.0.1", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "fast-glob": "^3.3.3", "is-glob": "^4.0.3", "semver": "^7.3.8" } }, "sha512-aOLc/aDR7lTWAHlytEefwn4Y6qs6uMr69DZvUx2A1AOAZsWhGB/paiRWPtVchh9wzMvLeqr+DkbENhVreVr9AQ=="], @@ -414,9 +413,7 @@ "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], @@ -424,20 +421,12 @@ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "boxen": ["boxen@8.0.1", "", { "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" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], - - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], - "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], @@ -450,23 +439,15 @@ "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], - "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], - "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], @@ -502,8 +483,6 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], - "devalue": ["devalue@5.6.3", "", {}, "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], @@ -524,13 +503,11 @@ "dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="], - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="], "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "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" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], @@ -540,7 +517,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@9.39.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.3", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg=="], + "eslint": ["eslint@10.0.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.2", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.1.1", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ=="], "eslint-compat-utils": ["eslint-compat-utils@0.6.5", "", { "dependencies": { "semver": "^7.5.4" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ=="], @@ -570,7 +547,7 @@ "estree-util-visit": ["estree-util-visit@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" } }, "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww=="], - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], @@ -590,6 +567,8 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], @@ -608,9 +587,9 @@ "fontkitten": ["fontkitten@1.0.2", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], - "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], @@ -622,8 +601,6 @@ "h3": ["h3@1.15.5", "", { "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" } }, "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg=="], - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "hast-util-embedded": ["hast-util-embedded@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-is-element": "^3.0.0" } }, "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA=="], "hast-util-format": ["hast-util-format@1.1.0", "", { "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" } }, "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA=="], @@ -676,10 +653,6 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], @@ -696,8 +669,6 @@ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], @@ -724,8 +695,6 @@ "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -756,8 +725,6 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], @@ -778,6 +745,8 @@ "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "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" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="], + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "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" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "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" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], @@ -816,6 +785,8 @@ "micromark-extension-directive": ["micromark-extension-directive@3.0.2", "", { "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" } }, "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA=="], + "micromark-extension-frontmatter": ["micromark-extension-frontmatter@2.0.0", "", { "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg=="], + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "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" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "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" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], @@ -884,7 +855,7 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "minimatch": ["minimatch@3.1.3", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA=="], + "minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], @@ -906,6 +877,8 @@ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], @@ -916,20 +889,18 @@ "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], + "p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], + "p-queue": ["p-queue@9.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw=="], - "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], + "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="], "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], "pagefind": ["pagefind@1.4.0", "", { "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" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g=="], - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - "parse-entities": ["parse-entities@4.0.2", "", { "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" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], "parse-latin": ["parse-latin@7.0.0", "", { "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" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], @@ -962,8 +933,6 @@ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], - "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], - "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], @@ -1002,8 +971,12 @@ "rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="], + "remark": ["remark@15.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="], + "remark-directive": ["remark-directive@3.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", "micromark-extension-directive": "^3.0.0", "unified": "^11.0.0" } }, "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A=="], + "remark-frontmatter": ["remark-frontmatter@5.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-frontmatter": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0", "unified": "^11.0.0" } }, "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ=="], + "remark-gfm": ["remark-gfm@4.0.1", "", { "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" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], @@ -1016,8 +989,6 @@ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="], "retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="], @@ -1046,11 +1017,11 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "shiki": ["shiki@3.22.0", "", { "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" } }, "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g=="], + "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], - "sitemap": ["sitemap@8.0.2", "", { "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/cli.js" } }, "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ=="], + "sitemap": ["sitemap@9.0.1", "", { "dependencies": { "@types/node": "^24.9.2", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/esm/cli.js" } }, "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ=="], "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="], @@ -1060,24 +1031,18 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "starlight-versions": ["starlight-versions@0.8.1", "", { "dependencies": { "@pagefind/default-ui": "^1.3.0", "github-slugger": "^2.0.0", "mdast-util-mdx-jsx": "^3.2.0", "mdast-util-mdxjs-esm": "^2.0.1", "remark": "^15.0.1", "remark-directive": "^4.0.0", "remark-frontmatter": "^5.0.0", "remark-mdx": "^3.1.1", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3", "yaml": "^2.8.2" }, "peerDependencies": { "@astrojs/starlight": ">=0.38.0" } }, "sha512-b8fGFnYDI1vC1x/kFuEchr7zBd6hY/V9UUUFsyxGK/ZejKVpLNOFAblvzVDj8Rp1tqbLXAhszcft9K1LuLSHVQ=="], + "stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], "suf-log": ["suf-log@2.5.3", "", { "dependencies": { "s.color": "0.0.15" } }, "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "svgo": ["svgo@4.0.0", "", { "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": "./bin/svgo.js" }, "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw=="], "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], @@ -1088,6 +1053,8 @@ "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + "tinyclip": ["tinyclip@0.1.12", "", {}, "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], @@ -1106,8 +1073,6 @@ "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], @@ -1116,6 +1081,8 @@ "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "unified": ["unified@11.0.5", "", { "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" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="], @@ -1162,35 +1129,25 @@ "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], - "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], - "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], - "yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="], - - "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], - - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - - "zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "@expressive-code/plugin-shiki/shiki": ["shiki@3.22.0", "", { "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" } }, "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g=="], - "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@mdx-js/mdx/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], @@ -1204,30 +1161,38 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "astro/@astrojs/compiler": ["@astrojs/compiler@3.0.0", "", {}, "sha512-MwAbDE5mawZ1SS+D8qWiHdprdME5Tlj2e0YjxnEICvcOpbSukNS7Sa7hA5PK+6RrmUr/t6Gi5YgrdZKjbO/WPQ=="], + + "astro/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "astro-eslint-parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0" } }, "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w=="], "astro-eslint-parser/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="], - "boxen/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "eslint/eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], + + "eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "eslint/espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + "eslint-plugin-astro/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="], + "estree-util-build-jsx/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "mdast-util-frontmatter/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -1236,15 +1201,23 @@ "postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], + "sitemap/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], + + "starlight-versions/remark-directive": ["remark-directive@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", "micromark-extension-directive": "^4.0.0", "unified": "^11.0.0" } }, "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA=="], + "vite/esbuild": ["esbuild@0.25.12", "", { "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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw=="], - "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA=="], - "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/langs": ["@shikijs/langs@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0" } }, "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/themes": ["@shikijs/themes@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0" } }, "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@3.22.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg=="], "astro-eslint-parser/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg=="], @@ -1252,6 +1225,8 @@ "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "starlight-versions/remark-directive/micromark-extension-directive": ["micromark-extension-directive@4.0.0", "", { "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" } }, "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg=="], + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -1304,10 +1279,6 @@ "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "astro-eslint-parser/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], } } diff --git a/package.json b/package.json index 980dfb6..b754b3c 100644 --- a/package.json +++ b/package.json @@ -10,20 +10,21 @@ "astro": "astro" }, "dependencies": { - "@astrojs/starlight": "^0.37.6", - "@astrojs/starlight-tailwind": "^4.0.2", + "@astrojs/starlight": "^0.38.1", + "@astrojs/starlight-tailwind": "^5.0.0", "@fontsource/ibm-plex-mono": "^5.2.7", "@fontsource/ibm-plex-sans": "^5.2.8", "@fontsource/ibm-plex-serif": "^5.2.7", "@tailwindcss/vite": "^4.2.1", - "astro": "^5.18.0", + "astro": "^6.0.4", "sharp": "^0.34.5", + "starlight-versions": "^0.8.1", "tailwindcss": "^4.2.1" }, "devDependencies": { - "@typescript-eslint/parser": "^8.56.1", + "@typescript-eslint/parser": "^8.57.0", "astro-eslint-parser": "^1.3.0", - "eslint": "^9.39.3", + "eslint": "^10.0.3", "eslint-plugin-astro": "^1.6.0", "prettier": "^3.8.1", "prettier-plugin-astro": "^0.14.1", diff --git a/src/assets/1.0/grlx.webp b/src/assets/1.0/grlx.webp new file mode 100644 index 0000000..be0afa9 --- /dev/null +++ b/src/assets/1.0/grlx.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5de242e9332bf9e47d1331e19604fc13e8d2eb9f32d8a56d53dd306831ee3c03 +size 30508 diff --git a/src/components/AdatomicFooter.astro b/src/components/AdatomicFooter.astro index 6e39078..24db22d 100644 --- a/src/components/AdatomicFooter.astro +++ b/src/components/AdatomicFooter.astro @@ -29,9 +29,12 @@ import Posthog from './Posthog.astro'

© 2026 ADAtomic, Inc. All rights reserved.

-
-

shop.grlx.dev

+
+

shop.grlx.dev

- + diff --git a/src/components/ArchitectureImage.astro b/src/components/ArchitectureImage.astro index 9f6482d..eb8a00d 100644 --- a/src/components/ArchitectureImage.astro +++ b/src/components/ArchitectureImage.astro @@ -1,14 +1,10 @@ --- import { Image } from 'astro:assets' -import darkArch from "../assets/grlx-arch-dark.webp" +import darkArch from '../assets/grlx-arch-dark.webp' import lightArch from '../assets/grlx-arch-light.webp' --- -ADAtomic logo +ADAtomic logo - !function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n diff --git a/src/content.config.ts b/src/content.config.ts new file mode 100644 index 0000000..7d5d65c --- /dev/null +++ b/src/content.config.ts @@ -0,0 +1,9 @@ +import { docsLoader } from '@astrojs/starlight/loaders' +import { docsSchema } from '@astrojs/starlight/schema' +import { defineCollection } from 'astro:content' +import { docsVersionsLoader } from 'starlight-versions/loader' + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), + versions: defineCollection({ loader: docsVersionsLoader() }), +} diff --git a/src/content/config.ts b/src/content/config.ts deleted file mode 100644 index 4624b9a..0000000 --- a/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content' -import { docsSchema, i18nSchema } from '@astrojs/starlight/schema' - -export const collections = { - docs: defineCollection({ schema: docsSchema() }), - i18n: defineCollection({ type: 'data', schema: i18nSchema() }), -} diff --git a/src/content/docs/1.0/architecture/overview.mdx b/src/content/docs/1.0/architecture/overview.mdx new file mode 100644 index 0000000..6afa229 --- /dev/null +++ b/src/content/docs/1.0/architecture/overview.mdx @@ -0,0 +1,29 @@ +--- +title: Overview +description: A brief description of the grlx architecture +slug: 1.0/architecture/overview +--- + +import ArchitectureImage from '../../../../components/ArchitectureImage.astro' + +At a high level the grlx architecture looks like the following: + + + +We can boil down the `grlx` into three different parts: the farmer, the sprout, and the CLI. + +## Farmer + +The farmer is your management server running the `grlx-farmer` binary. The binary can run as a systemd service or be hosted in a container. This has analogs in other similar configuration platforms such as a Salt Master in SaltStack or a Chef Server in Chef. grlx utilizes the farmer for things like authentication, job processing, and [ingredients](/1.0/architecture/overview#ingredients). Additionally, the farmer is responsible for holding your [recipes](/1.0/architecture/overview#recipes). + +## Sprout + +The sprout is a managed node running the `grlx-sprout` binary. Sprouts receive commands from the farmer over the NATS message bus. These can be immediate shell commands, or actions that are performed by Ingredients defined in a recipe (e.g. ensure that a file exists, a service is started). Sprouts are similar to a Salt Minion in SaltStack or a Chef Client in Chef. Jobs are received from the farmer over the NATS message bus. Sprouts will then execute the job and report back to the farmer the results of the job. + +## CLI + +The CLI is a tool used to interact with the farmer and manage sprouts. The CLI is used to accept keys from sprouts and `cook` (run) recipes that are hosted on the farmer. The CLI also contains tools to tail the traffic over the NATS bus, test sprout connections, and run arbitrary commands on sprouts. + +## Recipes + +Recipes are the core of grlx and are responsible for handling the configuration of your sprouts. Recipes are written with the `.grlx` extension and are a combination of YAML and Go templates. Recipes are stored on the farmer and are executed by the sprouts. Recipes are responsible for defining the [ingredients](/1.0/architecture/overview#ingredients) and in what order they are executed. For more information see the [recipes](/1.0/recipes/overview) section. diff --git a/src/content/docs/1.0/getting-started.mdx b/src/content/docs/1.0/getting-started.mdx new file mode 100644 index 0000000..8ef1aa9 --- /dev/null +++ b/src/content/docs/1.0/getting-started.mdx @@ -0,0 +1,266 @@ +--- +title: Getting Started +description: Getting Started with grlx +slug: 1.0/getting-started +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components' + +Want to get up and running as quickly as possible to see what all the fuss is about? +Use our bootstrap scripts! Follow our Quick Start to get started! + +## Quick Start + +### 1. Download and initialize the CLI + +Install the CLI on your development machine with the correct command for your architecture: + + + + + + ```bash + curl -L https://releases.grlx.dev/linux/amd64/latest/grlx > grlx && chmod +x grlx + ./grlx init + ``` + + + + ```bash + curl -L https://releases.grlx.dev/linux/386/latest/grlx > grlx && chmod +x grlx + ./grlx init + ``` + + + + ```bash + curl -L https://releases.grlx.dev/linux/arm/latest/grlx > grlx && chmod +x grlx + ./grlx init + ``` + + + + ```bash + curl -L https://releases.grlx.dev/linux/arm64/latest/grlx > grlx && chmod +x grlx + ./grlx init + ``` + + + + + + + + ```bash + curl -L https://releases.grlx.dev/darwin/amd64/latest/grlx > grlx && chmod +x grlx + ./grlx init + ``` + + + + ```bash + curl -L https://releases.grlx.dev/darwin/arm64/latest/grlx > grlx && chmod +x grlx + ./grlx init + ``` + + + + + +You'll be asked some questions, such as which interface the farmer is listening on, and which ports to use for communication. +Set the interface to the domain name or IP address of the farmer. + +If you plan to install the farmer on k3s (see below), then use the IP address of your k3s server, API port 30405, and Bus port 30406. If you plan to locally run the Docker Compose file below, then all of the defaults are good (localhost and default ports). + +Once configured, the CLI prints out your administrator public key, which you'll need for the next step! +It's recommended you now add `grlx` somewhere in your `$PATH`. + +*** + +### 2. Install the farmer on the management server + +You will then need to install the farmer on the management server. The farmer is the central server that manages the fleet. + + + + ```bash + # You need to perform this task as root + curl -L https://bootstrap.grlx.dev/latest/farmer | bash + ``` + + You'll be asked several questions about the interface to listen on, which ports to use, etc. + For the quick start, it's recommended to use the default ports (TCP 5405-5406). + You'll be prompted for an admin public key, which you should have gotten from the prior step, and a certificate host name(s). + Make sure the certificate host name matches the external-facing interface (a domain or IP address) as it will be used for TLS validation! + + + + Copy the resource YAML below. Replace `PASTE_YOUR_PUBLIC_KEY_HERE` with your admin public key, and `PASTE_YOUR_FARMERINTERFACE_HERE` with the domain name or IP address of the farmer. + + Deploy the farmer, e.g. `kubectl apply -f grlx-farmer.yaml`. This will deploy a single farmer to which you can add many external sprouts. + + ```yaml + apiVersion: v1 + kind: Service + metadata: + name: grlx-farmer-np + spec: + type: NodePort + ports: + - name: api + port: 5405 + nodePort: 30405 + - name: bus + port: 5406 + nodePort: 30406 + selector: + statefulset.kubernetes.io/pod-name: grlx-farmer-0 + --- + apiVersion: apps/v1 + kind: StatefulSet + metadata: + name: grlx-farmer + spec: + selector: + matchLabels: + app: grlx + role: farmer + serviceName: grlx-farmer + replicas: 1 + template: + metadata: + labels: + app: grlx + role: farmer + spec: + containers: + - name: main + image: grlx/farmer:latest + env: + - name: ADMIN_PUBKEYS + value: "PASTE_YOUR_PUBLIC_KEY_HERE" + - name: CERT_HOSTS + value: "localhost,PASTE_YOUR_FARMERINTERFACE_HERE" + - name: FARMERINTERFACE + value: "0.0.0.0" # do not modify + - name: FARMERORGANIZATION + value: "Your Organization" # change to your org name + ports: + - name: api + containerPort: 5405 + - name: bus + containerPort: 5406 + volumeMounts: + - name: cache + mountPath: /var/cache/grlx + - name: config + mountPath: /etc/grlx + - name: data + mountPath: /srv/grlx + volumes: + - name: cache + emptyDir: {} + - name: config + hostPath: + path: /etc/grlx + type: DirectoryOrCreate + - name: data + hostPath: + path: /srv/grlx + type: DirectoryOrCreate + ``` + + + + Copy the Docker Compose YAML below. Replace `PASTE_YOUR_PUBLIC_KEY_HERE` with your admin public key, then `docker compose up` to fire up an all-in-one farmer/sprout environment. + + ```yaml + version: "3" + services: + farmer: + container_name: farmer + image: grlx/farmer:latest + hostname: farmer + environment: + - ADMIN_PUBKEYS=PASTE_YOUR_PUBLIC_KEY_HERE + - CERT_HOSTS=localhost,farmer + - FARMERINTERFACE=0.0.0.0 + volumes: + - ./local/farmer:/etc/grlx + # This is where the farmer whill store its recipes + - ./local/data:/srv/grlx + ports: + - "5405:5405" + - "5406:5406" + sprout: # for testing grlx features only, not for production + container_name: sprout + image: grlx/sprout:latest + hostname: sprout + volumes: + - ./local/sprout:/etc/grlx + environment: + - FARMERINTERFACE=farmer + ``` + + + +*** + +### 3. Install the sprout on every node you want to manage + +The sprout is the agent that runs on every node you want to manage. It's responsible for executing commands and reporting back to the farmer. + +```bash +# Be sure to change FARMERINTERFACE to the domain or IP address of the farmer. +# FARMERBUSPORT and FARMERAPIPORT variables are available in case you need +# to use different ports (see k3s note below). +curl -L https://bootstrap.grlx.dev/latest/sprout | FARMERINTERFACE=localhost bash +``` + +NOTE: If you deployed the k3s farmer above, then use: + +```bash +# Be sure to change FARMERINTERFACE to the domain or IP address of the farmer. +curl -L https://bootstrap.grlx.dev/latest/sprout | FARMERBUSPORT=30406 FARMERAPIPORT=30405 FARMERINTERFACE=localhost bash +``` + +Once the sprout is up and running, return to the CLI. + +*** + +### 4. Accept the TLS cert and the sprout keys + +Next, you must accept each sprout's public key and the farmer's TLS certificate. This is done using the `keys` command. Furthermore, using `grlx version`, shows you that you are able to connect to your farmer. + +```bash +# You will be asked to download and trust the TLS certificate for +# the farmer, choose Y. +grlx version +grlx keys accept -A +``` + +*** + +### 5. Now you are ready to `cook`! + +```bash +grlx -T \* test ping +grlx -T \* cmd run whoami +grlx -T \* cmd run --out json -- uname -a +``` + +*** + +#### Uninstalling + +If you ever need to uninstall the `grlx-farmer`, you can do so with the following commands: + +```bash +curl -L https://bootstrap.grlx.dev/latest/farmer | UNINSTALL=true bash +``` + +Or the `grlx-sprout`: + +```bash +curl -L https://bootstrap.grlx.dev/latest/sprout | UNINSTALL=true bash +``` diff --git a/src/content/docs/1.0/glossary.md b/src/content/docs/1.0/glossary.md new file mode 100644 index 0000000..7bceb8d --- /dev/null +++ b/src/content/docs/1.0/glossary.md @@ -0,0 +1,61 @@ +--- +title: Glossary +description: A glossary of terms commonly used in grlx +slug: 1.0/glossary +--- + +## Farmer (grlx-farmer) + +The farmer is your management server running the `grlx-farmer` binary. The binary can run as a systemd service or be hosted in a container. This has analogs in other similar configuration platforms. In SaltStack, this is your Salt Master, or in Chef, your Chef Server. grlx utilizes the farmer for things like authentication, job processing, and [ingredients](/1.0/architecture/overview#ingredients). + +## Sprout (grlx-sprout) + +The sprout is a managed node running the `grlx-sprout` binary. Sprouts receive commands from the farmer over the NATS message bus. These can be immediate shell commands, or actions that are performed by Ingredients defined in a recipe (e.g. ensure that a file exists). Sprouts are similar to a Salt Minion in SaltStack or a Chef Client in Chef. + +## CLI (grlx CLI) + +The CLI is a tool used to interact with the farmer and manage sprouts. The CLI is used to accept keys from sprouts and `cook` (run) recipes that are hosted on the farmer. The CLI also contains tools to tail the traffic over the NAT bus, test sprout connections, and run arbitrary commands on sprouts. + +## Authentication + +All communication is encrypted using self-signed TLS certificates and [NATS.io NKey encryption](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/nkey_auth). Certificates are pinned to the clients on first connection as an extra security precaution. Furthermore, every CLI must accept a working certificate for any interactions with the farmer. NKeys provides a NATS native method for handling asymmetric encryption of messages between all parts of the system. + +## Command Execution + +The CLI has the ability to dispatch arbitrary shell commands to sprouts using `grlx cmd run`. Jobs are evaluated and dispatched to sprouts and their outputs are aggregated to the output of the CLI. Furthermore, this can be used inside of recipes for more structured deployments. + +## Farmer Hooks (hooks) + +Hooks provide a way to fire off webhooks when recipe deployment (cook) starts, fails or finishes. Additionally, you can configure a hook to fire when a new sprout is added to the farm, or when a sprout’s status has changed. A “hook” ingredient is also provided (not shown) that can be fired from the sprout during steps of a recipe. + +## Ingredients + +[Ingredients](/1.0/ingredients/overview) are how we build configurations with grlx. They can be thought of as the building blocks for completing various file, service, or management operations. Ingredients use a provider interface to allow for simple extensibility via the [Go plugin system](https://pkg.go.dev/plugin). + +## Template Engine + +Templating is achieved with [Go’s builtin templating engine](https://pkg.go.dev/text/template). This provides a Jinja-like templating system that hooks into the Farmer and Sprout properties to customize recipes to meet the dynamic needs of your needs. + +## Job Enqueuing and Processing + +You initiate jobs from the CLI by cooking recipes. Processing is continued on the farmer, and sprouts stream results to the CLI in real-time over the message bus. + +## Properties (Farmer, Sprout and Fetched Sprout Props) + +Both farmers and sprouts have properties for use in recipes. These properties are used in recipe templates to specify conditional execution of parts of the recipe. One example of this might be the specific OS or architecture of a sprout to ensure that the correct package gets installed on the correct machine. + +## API + +Each farmer has a REST API used for querying information about jobs or other information the farmer has access to. API tokens can be generated by the `grlx auth token`. + +## Local API + +The Local API is hosted on the grlx CLI to run a web frontend application for grlx. The local API provides a simple yet extensible interface for interacting with the CLI without the need for a terminal. + +## Message Bus + +An embedded [NATS.io server](https://nats.io/) is built into the farmer to handle connection between the sprouts, farmer, and the CLI. The bus uses NKeys to handle secure message passing between sprouts, the farmer, and CLIs. The message bus is responsible for handling communication between the CLI and the farmer as well as ensuring that jobs get dispatched to sprouts. + +## Managed Node + +The managed node is where the `grlx-sprout` daemon runs. It is the physical device where a sprout will deploy files, manage services, or run commands. diff --git a/src/content/docs/1.0/index.mdx b/src/content/docs/1.0/index.mdx new file mode 100644 index 0000000..f751ce3 --- /dev/null +++ b/src/content/docs/1.0/index.mdx @@ -0,0 +1,45 @@ +--- +title: grlx +description: Effective Fleet Configuration Management +template: splash +hero: + tagline: Effective Fleet Configuration Management + image: + file: ../../../assets/1.0/grlx.webp + actions: + - text: Get Started + link: /1.0/getting-started/ + icon: right-arrow + variant: primary + - text: Check out our GitHub + link: https://github.com/gogrlx/grlx + icon: github +slug: "1.0" +--- + +import { Card, CardGrid } from '@astrojs/starlight/components' + +## Next steps + + + + Check out our [Getting Started](/1.0/getting-started) started page on how to + install grlx + + + + Check out our [ingredients](/1.0/ingredients/overview) to learn more about how + to configure your fleet + + + + Check out the [Glossary](/1.0/glossary) for more info on grlx concepts + + +
+ + Join our [Discord](https://discord.gg/RNsZ3KWjXm) for learning more about + grlx + +
+
diff --git a/src/content/docs/1.0/ingredients/cmd.md b/src/content/docs/1.0/ingredients/cmd.md new file mode 100644 index 0000000..72b1f30 --- /dev/null +++ b/src/content/docs/1.0/ingredients/cmd.md @@ -0,0 +1,34 @@ +--- +title: grlx.ingredients.cmd +description: cmd +slug: 1.0/ingredients/cmd +--- + +The cmd ingredient allows you to run arbitrary shell commands against sprouts. + +## **cmd.run** + +Runs shell commands against a sprout + +#### Parameters + +| parameter | type | required | description | +| --------- | ------ | -------- | ---------------------------------------------------------------------------------------- | +| _name_ | string | true | the command to run | +| _runas_ | string | false | user who will run this command (defaults to root) | +| _path_ | string | false | the path to the binary, this is prepended to the name | +| _cwd_ | string | false | the directory to run this command | +| _env_ | list | false | environment variables to set should be specified like key1=value1 | +| _timeout_ | string | false | set a timeout for the command using [Go Duration](https://pkg.go.dev/time#ParseDuration) | + +```yaml +cmd.run: + - name: go version + - runas: super-cool-user + - path: /usr/local/bin + - cwd: /tmp + - env: + - GOOS=linux + - GOARCH=arm64 + - timeout: 10s +``` diff --git a/src/content/docs/1.0/ingredients/file-providers.md b/src/content/docs/1.0/ingredients/file-providers.md new file mode 100644 index 0000000..dc0149a --- /dev/null +++ b/src/content/docs/1.0/ingredients/file-providers.md @@ -0,0 +1,43 @@ +--- +title: grlx.ingredients.file.providers +description: grlx built-in file providers +slug: 1.0/ingredients/file-providers +--- + +`grlx` has a concept of file providers for different ways that you might obtain a file to be added to a given sprout. This uses a provider interface to keep this extensible and provide a standard way to use different file types. The `go` interface for File Providers looks like the following: + +```go +type FileProvider interface { + Download(context.Context) error + Properties() (map[string]interface{}, error) + Parse(id, source, destination, hash string, properties map[string]interface{}) (FileProvider, error) + Protocols() []string + Verify(context.Context) (bool, error) +} +``` + +By default, `grlx` has two built-in providers: local and HTTP. + +## Local + +The local provider would be how you would use a file from your host system. + +#### Example + +```yaml +file.cached: + - source: go1.21.3.src.tar.gz + - hash: sha256=186f2b6f8c8b704e696821b09ab2041a5c1ee13dcbc3156a13adcf75931ee488 +``` + +## HTTP + +The HTTP provider allows you to download files via HTTP for use. The [example](/1.0/ingredients/file-providers/#example) below is using the HTTP provider to download Go from the Internet. This file then gets cached to the sprout. + +#### Example + +```yaml +file.cached: + - source: https://go.dev/dl/go1.21.3.src.tar.gz + - hash: sha256=186f2b6f8c8b704e696821b09ab2041a5c1ee13dcbc3156a13adcf75931ee488 +``` diff --git a/src/content/docs/1.0/ingredients/file.mdx b/src/content/docs/1.0/ingredients/file.mdx new file mode 100644 index 0000000..af1f66b --- /dev/null +++ b/src/content/docs/1.0/ingredients/file.mdx @@ -0,0 +1,298 @@ +--- +title: grlx.ingredients.file +description: file +slug: 1.0/ingredients/file +--- + +import { Badge } from '@astrojs/starlight/components'; + +The file ingredient handles all file operations on various [file providers](/1.0/ingredients/file-providers) (such as local files, HTTP, etc.) + +## **file.absent** + +Deletes a file or directory + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | the name/path of the file to delete + +#### Example + +```yaml +file.absent: + name: ~/.config/systemd/user/backup.service +``` + +## **file.append** + +Appends content to a file. Only appends the content if it doesn't exist. + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes| the name/path of the file to append to +| *text* | string | no | the text to append to a file +| *makedirs* | bool | no | create parent directories if they do not exist +| *source* | string | no | append lines from a file sourced from this path/URL +| *source\_hash* | string | no | hash to verify the file specified by source +| *template* | bool | no | treat this file as a template and render it before placing +| *sources* | string/list | no | appends all content specified in list, checked in order +| *source\_hashes* | string/list | no | corresponding hashes for sources + +#### Example + +```yaml +file.append: + - name: /etc/profile + - text: | + export PATH=$PATH:/usr/local/go/bin +``` + +## **file.cached** + +Validates if a file is cached in the sprout's cache. If it isn't the file will be cached. + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | the path describing where to save the cached file +| *source* | string | yes | a file source (such as HTTP, file, etc.) to reference +| *hash* | string | conditional (required if `skip_verify` is false) | a valid hash of the `source` file +| *skip\_verify* | boolean | no | whether to skip hash validation, false by default + +#### Example + +```yaml +file.cached: + - name: /tmp/cachedfile + - source: https://go.dev/dl/go1.21.3.src.tar.gz + - hash: sha256=186f2b6f8c8b704e696821b09ab2041a5c1ee13dcbc3156a13adcf75931ee488 +``` + +## **file.contains** + +Checks if a file contains a given selection. If multiple sources are provided, all must be satisfied. + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | the name/path of the file to check +| *text* | string | no |the item to search for +| *source* | string | no | a file source (such as HTTP, file, etc.) to reference +| *source\_hash* | string | conditional (required if `source` provided) | a hash for a given source +| *sources* | list | no | a list of sources to check against +| *source\_hashes* | list | conditional (required if `skip_verify` is false) | a list of source hashes +| *skip\_verify* | boolean | no | whether to skip hash validation, false by default +| *template* | bool | no | treat this file as a template and render it before placing it + +## **file.content** + +Copies content into a given file + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | Represents the name of a file or directory +| *makedirs* | bool | no (default: `false`) | Determines whether directories should be created if they don't exist +| *source* | string | no | Represents a source file's path or name +| *source\_hash* | string | conditional (required if `source` is provided and `skip_verify` is `false`) | Represents the hash of a source file, used for verification +| *text* | string/list | no | Represents the content of a file. Can be a single string or a list of items +| *sources* | list | no | Represents multiple source files +| *template* | bool | no | treat this file as a template and render it before placing it +| *skip\_verify* | boolean | no | whether to skip hash validation, false by default +| *source\_hashes* | list | conditional (required if `sources` is provided and `skip_verify` is `false`) | Represents the hashes for the multiple source files mentioned in `sources` + +#### Example + +```yaml +file.content: + - name: /srv/nginx/nginx.conf + - makdirs: true + - text: | + server { + listen 8080; + root /data/up1; + + location / { + } + } +``` + +## **file.directory** + +Handles many directory operations. Ensures that a directory exists with the given permissions. + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | the name/path of the directory +| *makedirs* | bool | no |option to make directory if it doesn't exist, defaults to true +| *user* | string | no |the user who will own the directory +| *group* | string | no |the group who will own the directory +| *dir\_mode* | string | no |the directory mode +| *file\_mode* | string | no |the file mode to set +| *recurse* | bool | no |whether to recurse the directories and apply permissions + +#### Example + +```yaml +file.directory: + - name: /tmp/item + - makdirs: false + - user: grlx + - group: grlx + - dir_mode: 755 + - recurse: false +``` + +## **file.exists** + +Checks if a file exists. + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | the name/path of the file + +#### Example + +```yaml +file.exists: + - name: /tmp/exists +``` + +## **file.managed** + +Manage many properties of a file concurrently, safely + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | Represents the name of a file or directory +| *source* | string | no | Represents a source file's path or name +| *source\_hash* | string | conditional (required if `skip_verify` is false) | Represents the hash of a source file, used for verification +| *user* | string | no |the user who will own the directory +| *group* | string | no |the group who will own the directory +| *mode* | string | no |the file's mode +| *template* | bool | no | treat this file as a template and render it before placing it +| *makedirs* | bool | no (default: `false`) | Determines whether directories should be created if they don't exist +| *dir\_mode* | string | no |the directory mode +| *text* | string/list | no | Represents the content of a file. Can be a single string or a list of items +| *sources* | list | no | Represents multiple source files +| *skip\_verify* | boolean | no | whether to skip hash validation, false by default +| *source\_hashes* | list | conditional (required if `sources` is provided) | Represents the hashes for the multiple source files mentioned in `sources` + +#### Example + +```yaml +file.managed: + - name: /srv/nginx/nginx.conf + - makdirs: true + - user: pi + - group: wheel + - text: | + server { + listen 8080; + root /data/up1; + + location / { + } + } +``` + +## **file.missing** + +Checks if a file is missing. + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | the name/path of the file + +#### Example + +```yaml +file.missing: + - name: /tmp/missing +``` + +## **file.prepend** + +Prepends content to a file. Only prepends the content if it doesn't exist. + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes| the name/path of the file to prepend to +| *text* | string | no | the text to prepend to a file +| *makedirs* | bool | no | create parent directories if they do not exist +| *source* | string | no | prepend lines from a file sourced from this path/URL +| *source\_hash* | string | conditional (required if `source` is provided and `skip_verify` is `false`) | hash to verify the file specified by source +| *skip\_verify* | boolean | no | whether to skip hash validation, false by default +| *template* | bool | no | treat this file as a template and render it before placing it +| *sources* | string/list | no | list source, but in list format +| *source\_hashes* | string/list | no | corresponding hashes for sources + +#### Example + +```yaml +file.prepend: + - name: /etc/profile + - text: | + export PATH=$PATH:/usr/local/go/bin +``` + +## **file.symlink** + +Creates a symlink at `name` that points to `target` + +#### Parameters + +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | the name/path of the file +| *target* | string | yes | the target path to link to +| *makedirs* | string | no | make parent directories if missing +| *user* | string | no | the user who should own the symlink +| *group* | string | no | the group who should own the symlink +| *mode* | string | no | the desired filemode of the symlink + +#### Example + +```yaml +file.symlink: + - name: ~/localbash + - target: /usr/bin/bash + - user: pi + - group: wheel + - mode: "655" +``` + +## **file.touch** + +Performs a `touch` on a file +| parameter | type | required | description | +|-----------|------|----------|-------------| +| *name* | string | yes | the name/path of the file +| *atime* | time (RFC339) | no | the access time to set the file to, set to the current time by default +| *mtime* | time (RFC339) | no | the modified time to set the file to, sets to the current time by default +| *makedirs* | bool | yes | whether or not to create any provided parent directories, false by default + +```yaml +file.touch: + - name: /tmp/parent/new-file + - atime: Mon, 06 Nov 2023 00:00:00 +0000 + - mtime: Mon, 06 Nov 2023 00:00:00 +0000 + - makedirs: true +``` diff --git a/src/content/docs/1.0/ingredients/groups.md b/src/content/docs/1.0/ingredients/groups.md new file mode 100644 index 0000000..c1de5b9 --- /dev/null +++ b/src/content/docs/1.0/ingredients/groups.md @@ -0,0 +1,54 @@ +--- +title: grlx.ingredients.group +description: An overview of grlx.ingredients.group +slug: 1.0/ingredients/groups +--- + +The group ingredient handles group operations on sprouts. + +## **group.absent** + +Removes a group + +#### Parameters + +| parameter | type | required | description | +| --------- | ------ | -------- | ------------------------------- | +| _name_ | string | no | The name of the group to remove | + +```yaml +group.absent: + - name: sproutgroup +``` + +## **group.exists** + +Validates if a group exists + +#### Parameters + +| parameter | type | required | description | +| --------- | ------ | -------- | ----------------------- | +| _name_ | string | yes | The group name to check | + +```yaml +group.exists: + - name: sproutgroup +``` + +## **group.present** + +Creates a group if it does not exist + +#### Parameters + +| parameter | type | required | description | +| --------- | ------ | -------- | ------------------------------- | +| _name_ | string | yes | The name of the group to create | +| _gid_ | string | no | The GID of the group to create | + +```yaml +group.present: + - name: sproutgroup + - gid: '1107' +``` diff --git a/src/content/docs/1.0/ingredients/overview.md b/src/content/docs/1.0/ingredients/overview.md new file mode 100644 index 0000000..532fd4e --- /dev/null +++ b/src/content/docs/1.0/ingredients/overview.md @@ -0,0 +1,13 @@ +--- +title: Overview +description: The foundation of grlx +sidebar: + order: 1 +slug: 1.0/ingredients/overview +--- + +Recipe ingredients are how we build configurations with `grlx`. They can be thought of as the building blocks for completing various file, service, or management operations. Recipes rely on dependency injection via a provider system to provide extensible backends for various tasks. This also allows users to develop [go plugins](https://pkg.go.dev/plugin) to extend ingredient providers. + +:::note +The [file ingredient](/1.0/ingredients/file) already supports a few different file providers. Check out the list of file providers [here](/1.0/ingredients/file-providers). +::: diff --git a/src/content/docs/1.0/ingredients/service-providers.md b/src/content/docs/1.0/ingredients/service-providers.md new file mode 100644 index 0000000..4c63a22 --- /dev/null +++ b/src/content/docs/1.0/ingredients/service-providers.md @@ -0,0 +1,71 @@ +--- +title: grlx.ingredients.service.providers +description: grlx built-in service providers +slug: 1.0/ingredients/service-providers +--- + +grlx has a concept of service providers for different ways to interact with a sprout's various services. This uses a provider interface to keep this extensible and provide a standard way to interact with different service managers. The Go interface for Service Providers looks like the following: + +```go +type ServiceProvider interface { + Properties() (map[string]interface{}, error) + Parse(id, method string, properties map[string]interface{}) (ServiceProvider, error) + + Start(context.Context) error + Stop(context.Context) error + Status(context.Context) (string, error) + + Enable(context.Context) error + Disable(context.Context) error + IsEnabled(context.Context) (bool, error) + + IsRunning(context.Context) (bool, error) + Restart(context.Context) error + + Mask(context.Context) error + Unmask(context.Context) error + IsMasked(context.Context) (bool, error) + + InitName() string + IsInit() bool +} +``` + +grlx automatically detects which init system is running on each sprout and selects the appropriate provider. The following providers are built in: + +## systemd + +The `systemd` provider manages services on Linux systems using systemd. This is the most common provider and is auto-detected on systems where `systemctl` is available. + +#### Example + +```yaml +service.enabled: + - name: cronie.service + - userMode: false +``` + +## rc.d (BSD) + +The `rc.d` provider manages services on BSD systems (FreeBSD, NetBSD, OpenBSD, DragonFlyBSD) using the native rc.d init system. It is auto-detected on systems where `/etc/rc.d` or `/usr/local/etc/rc.d` is present. + +The rc.d provider maps grlx service operations to the corresponding `service(8)` and `rc.conf` commands: + +- **Start/Stop/Restart** — `service start|stop|restart` +- **Enable/Disable** — adds or removes `_enable="YES"` in `/etc/rc.conf` +- **Status** — `service status` + +#### Example + +```yaml +service.running: + - name: sshd +``` + +:::note +The `userMode` parameter is not applicable to rc.d services and is ignored if set. +::: + +:::note +The `mask`/`unmask` operations are supported on rc.d but use `rc.conf` variables to prevent service startup, since BSD rc.d does not have a native mask concept like systemd. +::: diff --git a/src/content/docs/1.0/ingredients/service.md b/src/content/docs/1.0/ingredients/service.md new file mode 100644 index 0000000..3266028 --- /dev/null +++ b/src/content/docs/1.0/ingredients/service.md @@ -0,0 +1,126 @@ +--- +title: grlx.ingredients.service +description: service +slug: 1.0/ingredients/service +--- + +The `service` ingredient handles the management of system services. grlx uses a provider interface, so the same recipe syntax works across different init systems. See [service providers](/1.0/ingredients/service-providers) for supported backends. + +## **service.masked** + +Checks if a service is masked and masks it if it is not + +#### Parameters + +| parameter | type | required | description | +| ---------- | ------ | -------- | --------------------------------------------- | +| _name_ | string | yes | the name of the service unit | +| _userMode_ | bool | no | to use service in user mode, false by default | + +```yaml +service.masked: + - name: ssd-backup + - userMode: true +``` + +## **service.unmasked** + +Checks if a service is unmasked and unmasks it if it is not + +#### Parameters + +| parameter | type | required | description | +| ---------- | ------ | -------- | --------------------------------------------- | +| _name_ | string | yes | the name of the service unit | +| _userMode_ | bool | no | to use service in user mode, false by default | + +```yaml +service.unmasked: + - name: ssd-backup + - userMode: true +``` + +## **service.running** + +Checks if a service is running and starts it if it is not + +#### Parameters + +| parameter | type | required | description | +| ---------- | ------ | -------- | --------------------------------------------- | +| _name_ | string | yes | the name of the service unit | +| _userMode_ | bool | no | to use service in user mode, false by default | + +```yaml +service.running: + - name: docker.service + - userMode: false +``` + +## **service.stopped** + +Checks if a service is stopped and stops it if it is not + +#### Parameters + +| parameter | type | required | description | +| ---------- | ------ | -------- | --------------------------------------------- | +| _name_ | string | yes | the name of the service unit | +| _userMode_ | bool | no | to use service in user mode, false by default | + +```yaml +service.stopped: + - name: docker.service + - userMode: false +``` + +## **service.enabled** + +Checks if a service is enabled and enables it if it is not + +#### Parameters + +| parameter | type | required | description | +| ---------- | ------ | -------- | --------------------------------------------- | +| _name_ | string | yes | the name of the service unit | +| _userMode_ | bool | no | to use service in user mode, false by default | + +```yaml +service.enabled: + - name: cronie.service + - userMode: false +``` + +## **service.disabled** + +Checks if a service is disabled and disables it if it is not + +#### Parameters + +| parameter | type | required | description | +| ---------- | ------ | -------- | --------------------------------------------- | +| _name_ | string | yes | the name of the service unit | +| _userMode_ | bool | no | to use service in user mode, false by default | + +```yaml +service.disabled: + - name: cronie.service + - userMode: false +``` + +## **service.restarted** + +Restarts a service + +#### Parameters + +| parameter | type | required | description | +| ---------- | ------ | -------- | --------------------------------------------- | +| _name_ | string | yes | the name of the service unit | +| _userMode_ | bool | no | to use service in user mode, false by default | + +```yaml +service.restarted: + - name: cronie.service + - userMode: false +``` diff --git a/src/content/docs/1.0/ingredients/user.md b/src/content/docs/1.0/ingredients/user.md new file mode 100644 index 0000000..9508825 --- /dev/null +++ b/src/content/docs/1.0/ingredients/user.md @@ -0,0 +1,65 @@ +--- +title: grlx.ingredients.user +description: An overview of grlx.ingredients.user +slug: 1.0/ingredients/user +--- + +The user ingredient handles user operations on sprouts. + +## **user.absent** + +Removes a user if it exists + +#### Parameters + +| parameter | type | required | description | +| --------- | ------ | -------- | ---------------------------------- | +| _name_ | string | yes | the name of the user to be removed | + +```yaml +user.absent: + - name: supersprout +``` + +## **user.exists** + +Validates if a user exists + +#### Parameters + +| parameter | type | required | description | +| --------- | ------ | -------- | --------------------- | +| _name_ | string | yes | The username to check | + +```yaml +user.exists: + - name: sproutuser +``` + +## **user.present** + +Creates a new user if it does not exist + +#### Parameters + +| parameter | type | required | description | +| --------- | ------------ | -------- | -------------------------------------------------------- | +| _name_ | string | yes | The username of the user | +| _uid_ | string | no | The UID assigned to the user | +| _gid_ | string | no | The GID to the user | +| _groups_ | list, string | no | A list groups the user is part of | +| _shell_ | string | no | The user login shell, /bin/false by default | +| _home_ | string | no | The path to the user's home directory if one is required | + +```yaml +user.present: + - name: sproutuser + - uid: '1003' + - gid: '1003' + - groups: + - wheel + - media + - sudo + - shell: /bin/bash + - home: /var/sproutuser +``` diff --git a/src/content/docs/1.0/recipes/overview.md b/src/content/docs/1.0/recipes/overview.md new file mode 100644 index 0000000..315ea68 --- /dev/null +++ b/src/content/docs/1.0/recipes/overview.md @@ -0,0 +1,56 @@ +--- +title: Overview +description: An overview of grlx recipes +slug: 1.0/recipes/overview +--- + +Recipes are the core of grlx functionality, providing a structured way for developers to deploy actions to sprouts. Recipes are a collection of ingredients which can be executed against a sprout. Recipes must be placed on the farmer at `/srv/grlx/recipes/prod` (with support for other non-prod environments in the future). Furthermore, recipes can utilize [Go's builtin templating engine](https://pkg.go.dev/text/template) to provide dynamic functionality for deployment. Below is a simple example of the makeup of a recipe. + +```yaml +include: + - .super-sprout-steps +steps: +{{ if eq (props hostname) super-sprout }} + install super-sprout: + file.touch: + - name: ~/super-sprout-config + requisites: + - require: super-sprout-steps-completed +{{ else }} + install normal-sprout: + file.touch: + - name: ~/normal-sprout-config +{{ end }} +``` + +In this example, an include is created for a `super-sprout-steps.grlx` file. This file would contain a list of steps with a final step `grlx-sprout-steps-completed`. Next we have our list of steps, bounded by Go templates. This template checks the hostname of the sprout and renders the steps to send to the farmer. In this case, if the sprout's hostname is `super-sprout`, then it will render a template that looks like this: + +```yaml +include: + - .super-sprout-steps +steps: + install super-sprout: + file.touch: + - name: ~/super-sprout-config + requisites: + - require: super-sprout-steps-completed +``` + +If the hostname is anything else, the sprout will run: + +```yaml +include: + - .super-sprout-steps +steps: + install normal-sprout: + file.touch: + - name: ~/normal-sprout-config +``` + +It is important to note the requisites listed. This ensures that the step from the include (`super-sprout-steps-completed`) occurs _before_ the `install-super-sprout` step is run. + +:::note +Deployment of recipe steps is non-deterministic unless requisites are provided. If order is important, then requisites **_must_** be specified. +::: + +Once you create a recipe, you can run it using `grlx cook -T `. In the case of the example above, I might run something like `grlx cook example -T \*` to run the above on all sprouts. You could also run `grlx cook example -T \* --out json` to get a json output of running this recipe against sprouts. diff --git a/src/content/docs/audit.md b/src/content/docs/audit.md new file mode 100644 index 0000000..135e43d --- /dev/null +++ b/src/content/docs/audit.md @@ -0,0 +1,70 @@ +--- +title: Audit Logging +description: Track all farmer actions with per-user attribution +--- + +grlx records every significant action performed through the farmer in an append-only audit log. Each entry captures who did what, when, on which target, and whether it succeeded. + +## What Gets Logged + +- Recipe executions (`cook`) +- Command dispatch (`cmd.run`) +- SSH sessions +- Key accept and reject operations +- Configuration changes + +Each entry includes: + +| Field | Description | +| --------- | -------------------------------- | +| Timestamp | When the action occurred | +| Username | The authenticated user who acted | +| Action | The type of operation | +| Target | The sprout or cohort affected | +| Result | Success or failure with detail | + +## Storage + +Audit logs are stored as JSONL (one JSON object per line) in the farmer's data directory, partitioned by date. This makes them easy to process with standard tools like `jq`, `grep`, or any log aggregation system. + +## CLI Commands + +### List available dates + +```bash +grlx audit dates +``` + +Shows all dates that have audit log entries, along with entry counts and file sizes. + +### Query audit entries + +```bash +# All entries for today +grlx audit list + +# Filter by date +grlx audit list --date 2026-03-15 + +# Filter by action type +grlx audit list --action cook + +# Filter by user +grlx audit list --pubkey UABC...XYZ + +# Show only failures +grlx audit list --failed + +# Limit results +grlx audit list --limit 50 +``` + +### JSON output + +```bash +grlx audit list --out json +``` + +## API Access + +The audit log is also available through the farmer's API at `GET /api/v1/audit`, supporting the same query parameters as the CLI. This endpoint requires at least `view` permission. diff --git a/src/content/docs/cohorts.md b/src/content/docs/cohorts.md new file mode 100644 index 0000000..93d064b --- /dev/null +++ b/src/content/docs/cohorts.md @@ -0,0 +1,108 @@ +--- +title: Cohorts +description: Group sprouts for targeted operations +--- + +Cohorts let you organize sprouts into named groups for targeting with `cook`, `cmd run`, `ssh`, and other operations. They replace ad-hoc glob patterns with reusable, manageable groupings. + +## Cohort Types + +### Static + +A fixed list of sprout names: + +```toml +[cohorts.webservers] +type = "static" +members = ["web-01", "web-02", "web-03"] +``` + +### Dynamic + +Membership is determined by matching a sprout property at evaluation time: + +```toml +[cohorts.linux-hosts] +type = "dynamic" +match_prop = "os" +match_value = "linux" +``` + +Dynamic cohorts automatically include any sprout whose property matches the condition. Use `grlx cohorts refresh` to re-evaluate membership against currently connected sprouts. + +### Compound + +Combine other cohorts with boolean logic: + +```toml +[cohorts.prod-linux] +type = "compound" +operator = "AND" +operands = ["production", "linux-hosts"] +``` + +Supported operators: + +| Operator | Description | +| -------- | ------------------------------------------------ | +| `AND` | Sprouts that belong to **all** listed cohorts | +| `OR` | Sprouts that belong to **any** listed cohort | +| `EXCEPT` | Sprouts in the first cohort but **not** the rest | + +Compound cohorts can reference other compound cohorts (nesting), with a depth limit to prevent cycles. + +## CLI Commands + +### List all cohorts + +```bash +grlx cohorts list +``` + +### Show cohort details + +```bash +grlx cohorts show webservers +``` + +Displays the cohort type, configuration, and resolved member list. + +### Refresh dynamic membership + +```bash +# Refresh a specific cohort +grlx cohorts refresh webservers + +# Refresh all cohorts +grlx cohorts refresh +``` + +Re-evaluates dynamic cohort membership against currently connected sprouts and their properties. + +### JSON output + +All cohort commands support `--out json` for machine-readable output: + +```bash +grlx cohorts list --out json +grlx cohorts show webservers --out json +``` + +## Using Cohorts + +Cohorts can be used anywhere you target sprouts: + +```bash +# Cook a recipe against a cohort +grlx cook nginx-setup -C webservers + +# Run a command on a cohort +grlx cmd run "uptime" -C linux-hosts + +# SSH with cohort picker +grlx ssh -C webservers +``` + +## RBAC Integration + +RBAC permissions are assigned per-cohort. A user might have `cook` and `view` permissions on the `staging` cohort but only `view` on `production`. See the [RBAC documentation](/rbac) for details. diff --git a/src/content/docs/glossary.md b/src/content/docs/glossary.md index 1f0c61f..7b56b40 100644 --- a/src/content/docs/glossary.md +++ b/src/content/docs/glossary.md @@ -2,45 +2,71 @@ title: Glossary description: A glossary of terms commonly used in grlx --- + ## Farmer (grlx-farmer) + The farmer is your management server running the `grlx-farmer` binary. The binary can run as a systemd service or be hosted in a container. This has analogs in other similar configuration platforms. In SaltStack, this is your Salt Master, or in Chef, your Chef Server. grlx utilizes the farmer for things like authentication, job processing, and [ingredients](/architecture/overview#ingredients). ## Sprout (grlx-sprout) + The sprout is a managed node running the `grlx-sprout` binary. Sprouts receive commands from the farmer over the NATS message bus. These can be immediate shell commands, or actions that are performed by Ingredients defined in a recipe (e.g. ensure that a file exists). Sprouts are similar to a Salt Minion in SaltStack or a Chef Client in Chef. ## CLI (grlx CLI) + The CLI is a tool used to interact with the farmer and manage sprouts. The CLI is used to accept keys from sprouts and `cook` (run) recipes that are hosted on the farmer. The CLI also contains tools to tail the traffic over the NAT bus, test sprout connections, and run arbitrary commands on sprouts. ## Authentication + All communication is encrypted using self-signed TLS certificates and [NATS.io NKey encryption](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/nkey_auth). Certificates are pinned to the clients on first connection as an extra security precaution. Furthermore, every CLI must accept a working certificate for any interactions with the farmer. NKeys provides a NATS native method for handling asymmetric encryption of messages between all parts of the system. +## Cohorts + +[Cohorts](/cohorts) let you organize sprouts into named groups (static, dynamic, or compound) for targeting with `cook`, `cmd run`, `ssh`, and other operations. + ## Command Execution + The CLI has the ability to dispatch arbitrary shell commands to sprouts using `grlx cmd run`. Jobs are evaluated and dispatched to sprouts and their outputs are aggregated to the output of the CLI. Furthermore, this can be used inside of recipes for more structured deployments. ## Farmer Hooks (hooks) + Hooks provide a way to fire off webhooks when recipe deployment (cook) starts, fails or finishes. Additionally, you can configure a hook to fire when a new sprout is added to the farm, or when a sprout’s status has changed. A “hook” ingredient is also provided (not shown) that can be fired from the sprout during steps of a recipe. ## Ingredients + [Ingredients](/ingredients/overview) are how we build configurations with grlx. They can be thought of as the building blocks for completing various file, service, or management operations. Ingredients use a provider interface to allow for simple extensibility via the [Go plugin system](https://pkg.go.dev/plugin). ## Template Engine + Templating is achieved with [Go’s builtin templating engine](https://pkg.go.dev/text/template). This provides a Jinja-like templating system that hooks into the Farmer and Sprout properties to customize recipes to meet the dynamic needs of your needs. ## Job Enqueuing and Processing + You initiate jobs from the CLI by cooking recipes. Processing is continued on the farmer, and sprouts stream results to the CLI in real-time over the message bus. ## Properties (Farmer, Sprout and Fetched Sprout Props) + Both farmers and sprouts have properties for use in recipes. These properties are used in recipe templates to specify conditional execution of parts of the recipe. One example of this might be the specific OS or architecture of a sprout to ensure that the correct package gets installed on the correct machine. +## RBAC + +[Role-based access control](/rbac) governs which users can perform which actions on which cohorts. Permissions are tied to CLI public keys. See also [Audit Logging](/audit) for action tracking. + +## SSH + +[`grlx ssh`](/ssh) opens an interactive shell on a sprout over the NATS message bus — no direct network access required. + ## API -Each farmer has a REST API used for querying information about jobs or other information the farmer has access to. API tokens can be generated by the `grlx auth token`. + +Each farmer has a REST API used for querying information about jobs or other information the farmer has access to. API tokens can be generated by the `grlx auth token`. ## Local API + The Local API is hosted on the grlx CLI to run a web frontend application for grlx. The local API provides a simple yet extensible interface for interacting with the CLI without the need for a terminal. ## Message Bus -An embedded [NATS.io server](https://nats.io/) is built into the farmer to handle connection between the sprouts, farmer, and the CLI. The bus uses NKeys to handle secure message passing between sprouts, the farmer, and CLIs. The message bus is responsible for handling communication between the CLI and the farmer as well as ensuring that jobs get dispatched to sprouts. + +An embedded [NATS.io server](https://nats.io/) is built into the farmer to handle connection between the sprouts, farmer, and the CLI. The bus uses NKeys to handle secure message passing between sprouts, the farmer, and CLIs. The message bus is responsible for handling communication between the CLI and the farmer as well as ensuring that jobs get dispatched to sprouts. ## Managed Node -The managed node is where the `grlx-sprout` daemon runs. It is the physical device where a sprout will deploy files, manage services, or run commands. +The managed node is where the `grlx-sprout` daemon runs. It is the physical device where a sprout will deploy files, manage services, or run commands. diff --git a/src/content/docs/ingredients/cmd.md b/src/content/docs/ingredients/cmd.md index acdd60f..fdda8e1 100644 --- a/src/content/docs/ingredients/cmd.md +++ b/src/content/docs/ingredients/cmd.md @@ -1,28 +1,33 @@ --- title: grlx.ingredients.cmd -description: cmd +description: cmd --- + The cmd ingredient allows you to run arbitrary shell commands against sprouts. + ## **cmd.run** + Runs shell commands against a sprout + #### Parameters -| parameter | type | required | description | -|-----------|------|----------|-------------| -| _name_ | string | true | the command to run -| _runas_ | string | false | user who will run this command (defaults to root) -| _path_ | string | false | the path to the binary, this is prepended to the name -| _cwd_ | string | false | the directory to run this command -| _env_ | list | false | environment variables to set should be specified like key1=value1 -| _timeout_ | string | false | set a timeout for the command using [Go Duration](https://pkg.go.dev/time#ParseDuration) + +| parameter | type | required | description | +| --------- | ------ | -------- | ---------------------------------------------------------------------------------------- | +| _name_ | string | true | the command to run | +| _runas_ | string | false | user who will run this command (defaults to root) | +| _path_ | string | false | the path to the binary, this is prepended to the name | +| _cwd_ | string | false | the directory to run this command | +| _env_ | list | false | environment variables to set should be specified like key1=value1 | +| _timeout_ | string | false | set a timeout for the command using [Go Duration](https://pkg.go.dev/time#ParseDuration) | + ```yaml cmd.run: - - name: go version - - runas: super-cool-user - - path: /usr/local/bin - - cwd: /tmp - - env: + - name: go version + - runas: super-cool-user + - path: /usr/local/bin + - cwd: /tmp + - env: - GOOS=linux - GOARCH=arm64 - - timeout: 10s + - timeout: 10s ``` - diff --git a/src/content/docs/ingredients/file-providers.md b/src/content/docs/ingredients/file-providers.md index 6b2dbe2..5c52a75 100644 --- a/src/content/docs/ingredients/file-providers.md +++ b/src/content/docs/ingredients/file-providers.md @@ -2,7 +2,9 @@ title: grlx.ingredients.file.providers description: grlx built-in file providers --- + `grlx` has a concept of file providers for different ways that you might obtain a file to be added to a given sprout. This uses a provider interface to keep this extensible and provide a standard way to use different file types. The `go` interface for File Providers looks like the following: + ```go type FileProvider interface { Download(context.Context) error @@ -12,22 +14,29 @@ type FileProvider interface { Verify(context.Context) (bool, error) } ``` -By default, `grlx` has two built-in providers: local and HTTP. + +By default, `grlx` has two built-in providers: local and HTTP. ## Local + The local provider would be how you would use a file from your host system. + #### Example + ```yaml file.cached: - - source: go1.21.3.src.tar.gz - - hash: sha256=186f2b6f8c8b704e696821b09ab2041a5c1ee13dcbc3156a13adcf75931ee488 + - source: go1.21.3.src.tar.gz + - hash: sha256=186f2b6f8c8b704e696821b09ab2041a5c1ee13dcbc3156a13adcf75931ee488 ``` ## HTTP -The HTTP provider allows you to download files via HTTP for use. The [example](/ingredients/file-providers/#example) below is using the HTTP provider to download Go from the Internet. This file then gets cached to the sprout. + +The HTTP provider allows you to download files via HTTP for use. The [example](/ingredients/file-providers/#example) below is using the HTTP provider to download Go from the Internet. This file then gets cached to the sprout. + #### Example + ```yaml file.cached: - - source: https://go.dev/dl/go1.21.3.src.tar.gz - - hash: sha256=186f2b6f8c8b704e696821b09ab2041a5c1ee13dcbc3156a13adcf75931ee488 + - source: https://go.dev/dl/go1.21.3.src.tar.gz + - hash: sha256=186f2b6f8c8b704e696821b09ab2041a5c1ee13dcbc3156a13adcf75931ee488 ``` diff --git a/src/content/docs/ingredients/groups.md b/src/content/docs/ingredients/groups.md index 7ca5622..ccab7f7 100644 --- a/src/content/docs/ingredients/groups.md +++ b/src/content/docs/ingredients/groups.md @@ -2,38 +2,52 @@ title: grlx.ingredients.group description: An overview of grlx.ingredients.group --- + The group ingredient handles group operations on sprouts. + ## **group.absent** + Removes a group + #### Parameters -| parameter | type | required | description | -|-----------|------|----------|-------------| -| _name_ | string | no | The name of the group to remove + +| parameter | type | required | description | +| --------- | ------ | -------- | ------------------------------- | +| _name_ | string | no | The name of the group to remove | + ```yaml group.absent: - name: sproutgroup ``` ## **group.exists** + Validates if a group exists + #### Parameters -| parameter | type | required | description | -|-----------|------|----------|-------------| -| _name_ | string | yes | The group name to check + +| parameter | type | required | description | +| --------- | ------ | -------- | ----------------------- | +| _name_ | string | yes | The group name to check | + ```yaml group.exists: - name: sproutgroup ``` ## **group.present** + Creates a group if it does not exist + #### Parameters -| parameter | type | required | description | -|-----------|------|----------|-------------| -| _name_ | string | yes | The name of the group to create -| _gid_ | string | no | The GID of the group to create + +| parameter | type | required | description | +| --------- | ------ | -------- | ------------------------------- | +| _name_ | string | yes | The name of the group to create | +| _gid_ | string | no | The GID of the group to create | + ```yaml group.present: - name: sproutgroup - - gid: "1107" + - gid: '1107' ``` diff --git a/src/content/docs/ingredients/overview.md b/src/content/docs/ingredients/overview.md index 583e4b3..fea4a41 100644 --- a/src/content/docs/ingredients/overview.md +++ b/src/content/docs/ingredients/overview.md @@ -2,9 +2,10 @@ title: Overview description: The foundation of grlx sidebar: - order: 1 + order: 1 --- -Recipe ingredients are how we build configurations with `grlx`. They can be thought of as the building blocks for completing various file, service, or management operations. Recipes rely on dependency injection via a provider system to provide extensible backends for various tasks. This also allows users to develop [go plugins](https://pkg.go.dev/plugin) to extend ingredient providers. + +Recipe ingredients are how we build configurations with `grlx`. They can be thought of as the building blocks for completing various file, service, or management operations. Recipes rely on dependency injection via a provider system to provide extensible backends for various tasks. This also allows users to develop [go plugins](https://pkg.go.dev/plugin) to extend ingredient providers. :::note The [file ingredient](/ingredients/file) already supports a few different file providers. Check out the list of file providers [here](/ingredients/file-providers). ::: diff --git a/src/content/docs/ingredients/service-providers.md b/src/content/docs/ingredients/service-providers.md index fc3eea3..4df4f63 100644 --- a/src/content/docs/ingredients/service-providers.md +++ b/src/content/docs/ingredients/service-providers.md @@ -2,7 +2,9 @@ title: grlx.ingredients.service.providers description: grlx built-in service providers --- + grlx has a concept of service providers for different ways to interact with a sprout's various services. This uses a provider interface to keep this extensible and provide a standard way to interact with different service managers. The Go interface for Service Providers looks like the following: + ```go type ServiceProvider interface { Properties() (map[string]interface{}, error) @@ -35,6 +37,7 @@ grlx automatically detects which init system is running on each sprout and selec The `systemd` provider manages services on Linux systems using systemd. This is the most common provider and is auto-detected on systems where `systemctl` is available. #### Example + ```yaml service.enabled: - name: cronie.service @@ -46,11 +49,13 @@ service.enabled: The `rc.d` provider manages services on BSD systems (FreeBSD, NetBSD, OpenBSD, DragonFlyBSD) using the native rc.d init system. It is auto-detected on systems where `/etc/rc.d` or `/usr/local/etc/rc.d` is present. The rc.d provider maps grlx service operations to the corresponding `service(8)` and `rc.conf` commands: + - **Start/Stop/Restart** — `service start|stop|restart` - **Enable/Disable** — adds or removes `_enable="YES"` in `/etc/rc.conf` - **Status** — `service status` #### Example + ```yaml service.running: - name: sshd diff --git a/src/content/docs/ingredients/user.md b/src/content/docs/ingredients/user.md index ed3dba6..f0cb855 100644 --- a/src/content/docs/ingredients/user.md +++ b/src/content/docs/ingredients/user.md @@ -2,50 +2,63 @@ title: grlx.ingredients.user description: An overview of grlx.ingredients.user --- + The user ingredient handles user operations on sprouts. + ## **user.absent** + Removes a user if it exists + #### Parameters -| parameter | type | required | description | -|-----------|------|----------|-------------| -| _name_ | string | yes | the name of the user to be removed + +| parameter | type | required | description | +| --------- | ------ | -------- | ---------------------------------- | +| _name_ | string | yes | the name of the user to be removed | + ```yaml user.absent: - name: supersprout ``` ## **user.exists** + Validates if a user exists + #### Parameters -| parameter | type | required | description | -|-----------|------|----------|-------------| -| _name_ | string | yes | The username to check + +| parameter | type | required | description | +| --------- | ------ | -------- | --------------------- | +| _name_ | string | yes | The username to check | + ```yaml user.exists: - name: sproutuser ``` ## **user.present** + Creates a new user if it does not exist + #### Parameters -| parameter | type | required | description | -|-----------|------|----------|-------------| -| _name_ | string | yes | The username of the user -| _uid_ | string | no | The UID assigned to the user -| _gid_ | string | no | The GID to the user -| _groups_ | list, string | no | A list groups the user is part of -| _shell_ | string | no | The user login shell, /bin/false by default -| _home_ | string | no | The path to the user's home directory if one is required + +| parameter | type | required | description | +| --------- | ------------ | -------- | -------------------------------------------------------- | +| _name_ | string | yes | The username of the user | +| _uid_ | string | no | The UID assigned to the user | +| _gid_ | string | no | The GID to the user | +| _groups_ | list, string | no | A list groups the user is part of | +| _shell_ | string | no | The user login shell, /bin/false by default | +| _home_ | string | no | The path to the user's home directory if one is required | + ```yaml user.present: - name: sproutuser - - uid: "1003" - - gid: "1003" + - uid: '1003' + - gid: '1003' - groups: - - wheel - - media - - sudo + - wheel + - media + - sudo - shell: /bin/bash - home: /var/sproutuser ``` - diff --git a/src/content/docs/rbac.md b/src/content/docs/rbac.md new file mode 100644 index 0000000..c5b24a8 --- /dev/null +++ b/src/content/docs/rbac.md @@ -0,0 +1,131 @@ +--- +title: RBAC +description: Role-based access control for users and cohorts +--- + +grlx includes a role-based access control (RBAC) system that governs who can perform actions on which sprouts. Permissions are tied to public keys, so every CLI user has an auditable identity. + +## Concepts + +- **User**: Identified by their CLI public key. Each key maps to a username and role. +- **Role**: A named set of permissions. Roles define which actions a user can perform on which cohorts. +- **Action**: An operation type — `cook`, `cmd`, `ssh`, or `view`. +- **Cohort scope**: Permissions are granted per-cohort. A user may have different permissions on different cohorts. + +## Configuration + +Roles and users are defined in the farmer configuration file: + +```toml +[rbac.roles.operator] +actions = ["cook", "cmd", "ssh", "view"] +cohorts = ["*"] + +[rbac.roles.deployer] +actions = ["cook", "view"] +cohorts = ["staging", "production"] + +[rbac.roles.viewer] +actions = ["view"] +cohorts = ["*"] +``` + +Users are mapped to roles by their public key: + +```toml +[[rbac.users]] +name = "alice" +pubkey = "UABC...XYZ" +role = "operator" + +[[rbac.users]] +name = "bob" +pubkey = "UDEF...UVW" +role = "deployer" +``` + +### Built-in Viewer Role + +Users with only the `view` action can: + +- List sprouts, jobs, props, cohorts +- Read job details and audit logs + +They **cannot** cook recipes, run commands, or open SSH sessions. + +## Actions + +| Action | Description | +| ------ | ------------------------------------ | +| `cook` | Execute recipes on sprouts | +| `cmd` | Run arbitrary commands via `cmd run` | +| `ssh` | Open interactive shell sessions | +| `view` | Read-only access to all resources | + +## Validation + +The farmer validates the RBAC configuration at startup: + +- Referenced cohorts must exist in the cohort configuration +- Public keys must be unique across all users +- Role names must be unique + +If validation fails, the farmer will not start and will report the configuration errors. + +## CLI Commands + +### Check your identity + +```bash +grlx auth whoami +``` + +Shows the current CLI user's public key and assigned role. + +### List users + +```bash +grlx auth users +``` + +### List roles + +```bash +grlx auth roles +``` + +### Explain effective permissions + +```bash +grlx auth explain +``` + +Shows the current user's effective permissions across all cohorts — which actions are allowed where. + +## Enforcement + +Every request to the farmer is checked against RBAC before execution. If the requesting user lacks the required action on the target cohort, the request is denied with a clear error message. + +The enforcement middleware covers: + +- `cook` — recipe execution +- `cmd.run` — arbitrary command dispatch +- `ssh` — interactive shell sessions +- Read endpoints — restricted to users with at least `view` permission + +## `dangerously_allow_root` + +For development or single-user setups, the farmer supports a `dangerously_allow_root` flag that bypasses RBAC checks: + +```toml +[farmer] +dangerously_allow_root = true +``` + +:::caution +This disables all permission checks. Do not use in production environments. The farmer logs a startup warning when this flag is enabled. +::: + +## Audit Logging + +All actions are recorded in the audit log with the acting user's identity. See [Audit Logging](/audit) for details. diff --git a/src/content/docs/recipes/overview.md b/src/content/docs/recipes/overview.md index 6e11e1b..404b60b 100644 --- a/src/content/docs/recipes/overview.md +++ b/src/content/docs/recipes/overview.md @@ -2,7 +2,9 @@ title: Overview description: An overview of grlx recipes --- + Recipes are the core of grlx functionality, providing a structured way for developers to deploy actions to sprouts. Recipes are a collection of ingredients which can be executed against a sprout. Recipes must be placed on the farmer at `/srv/grlx/recipes/prod` (with support for other non-prod environments in the future). Furthermore, recipes can utilize [Go's builtin templating engine](https://pkg.go.dev/text/template) to provide dynamic functionality for deployment. Below is a simple example of the makeup of a recipe. + ```yaml include: - .super-sprout-steps @@ -19,7 +21,9 @@ steps: - name: ~/normal-sprout-config {{ end }} ``` + In this example, an include is created for a `super-sprout-steps.grlx` file. This file would contain a list of steps with a final step `grlx-sprout-steps-completed`. Next we have our list of steps, bounded by Go templates. This template checks the hostname of the sprout and renders the steps to send to the farmer. In this case, if the sprout's hostname is `super-sprout`, then it will render a template that looks like this: + ```yaml include: - .super-sprout-steps @@ -30,7 +34,9 @@ steps: requisites: - require: super-sprout-steps-completed ``` + If the hostname is anything else, the sprout will run: + ```yaml include: - .super-sprout-steps @@ -40,9 +46,9 @@ steps: - name: ~/normal-sprout-config ``` -It is important to note the requisites listed. This ensures that the step from the include (`super-sprout-steps-completed`) occurs _before_ the `install-super-sprout` step is run. +It is important to note the requisites listed. This ensures that the step from the include (`super-sprout-steps-completed`) occurs _before_ the `install-super-sprout` step is run. :::note -Deployment of recipe steps is non-deterministic unless requisites are provided. If order is important, then requisites ***must*** be specified. +Deployment of recipe steps is non-deterministic unless requisites are provided. If order is important, then requisites **_must_** be specified. ::: Once you create a recipe, you can run it using `grlx cook -T `. In the case of the example above, I might run something like `grlx cook example -T \*` to run the above on all sprouts. You could also run `grlx cook example -T \* --out json` to get a json output of running this recipe against sprouts. diff --git a/src/content/docs/sprouts.md b/src/content/docs/sprouts.md new file mode 100644 index 0000000..581478d --- /dev/null +++ b/src/content/docs/sprouts.md @@ -0,0 +1,46 @@ +--- +title: Managing Sprouts +description: List, inspect, and monitor connected sprouts +--- + +The `grlx sprouts` command provides visibility into all sprouts known to the farmer — their connection status, key state, and properties. + +## List Sprouts + +```bash +grlx sprouts list +``` + +Shows all known sprouts sorted by connection status (connected first, then alphabetical). The output includes each sprout's name, connection state, and key state. + +### Filter by state + +```bash +# Only show sprouts with accepted keys +grlx sprouts list --state accepted + +# Only show currently connected sprouts +grlx sprouts list --online +``` + +### JSON output + +```bash +grlx sprouts list --out json +``` + +## Inspect a Sprout + +```bash +grlx sprouts show my-sprout +``` + +Displays detailed information about a single sprout, including its properties, connection history, and current status. + +## Key Management + +Before a sprout can receive commands, the farmer must accept its key. This is handled through the `grlx auth` subcommands rather than the sprouts command — see the [Authentication section](/glossary#authentication) for details. + +## Connection Model + +Sprouts connect to the farmer's embedded NATS server on startup. The farmer tracks which sprouts are currently connected and their last-seen timestamps. Sprouts behind firewalls or NAT work fine — they only need outbound access to the farmer's NATS port. diff --git a/src/content/docs/ssh.md b/src/content/docs/ssh.md new file mode 100644 index 0000000..96045c7 --- /dev/null +++ b/src/content/docs/ssh.md @@ -0,0 +1,51 @@ +--- +title: SSH +description: Open interactive shell sessions on sprouts over NATS +--- + +grlx provides a built-in remote shell that works entirely over the NATS message bus. No direct SSH access or network connectivity to the sprout is required — the farmer validates the request and the sprout spawns a PTY-backed shell process. + +## Basic Usage + +Connect to a sprout by name: + +```bash +grlx ssh my-sprout +``` + +This opens an interactive terminal session. Press `Ctrl-D` or type `exit` to end it. + +### Specifying a Shell + +By default the sprout uses `/bin/sh`. Override with `--shell`: + +```bash +grlx ssh my-sprout --shell /bin/bash +``` + +## Cohort Targeting + +Instead of naming a sprout directly, target a cohort with `-C` / `--cohort`: + +```bash +grlx ssh -C webservers +``` + +If the cohort resolves to a single sprout, the connection opens immediately. If multiple sprouts match, an interactive picker is displayed so you can choose which one to connect to. + +:::note +You cannot specify both a sprout name and `--cohort` at the same time. +::: + +## How It Works + +1. The CLI sends a shell request to the farmer over NATS. +2. The farmer validates the requesting user's identity and RBAC permissions. +3. The target sprout spawns a PTY-backed shell process. +4. Input and output are relayed through NATS in real time. + +Because the connection runs over the message bus, sprouts behind firewalls or NAT are fully accessible — as long as they can reach the farmer's NATS server. + +## RBAC + +Shell access is governed by the `ssh` action in the RBAC permission model. Users without the `ssh` permission on the target sprout's cohort will be denied. See the [RBAC documentation](/rbac) for details on configuring permissions. diff --git a/src/content/versions/1.0.json b/src/content/versions/1.0.json new file mode 100644 index 0000000..c030e96 --- /dev/null +++ b/src/content/versions/1.0.json @@ -0,0 +1,30 @@ +{ + "sidebar": [ + { + "label": "Getting Started", + "link": "/getting-started" + }, + { + "label": "Architecture", + "autogenerate": { + "directory": "architecture" + } + }, + { + "label": "Ingredients", + "autogenerate": { + "directory": "ingredients" + } + }, + { + "label": "Recipes", + "autogenerate": { + "directory": "recipes" + } + }, + { + "label": "Glossary", + "link": "/glossary" + } + ] +} \ No newline at end of file diff --git a/src/custom.css b/src/custom.css index 955e1fa..5d1604c 100644 --- a/src/custom.css +++ b/src/custom.css @@ -1,9 +1,9 @@ -.discord-bg .card .icon{ - background-color: #5865F2; - --sl-card-border: #454FBF !important; +.discord-bg .card .icon { + background-color: #5865f2; + --sl-card-border: #454fbf !important; } -[data-theme="light"] .hidden{ +[data-theme='light'] .hidden { display: none; }