From 2b65c832458d17e0a745c14d8ec791472607482e Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 13:40:34 -0400 Subject: [PATCH 01/64] temp rm uva --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd5d7e3..5db7ea5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,9 @@ name: CI +# Runners: uvacompute (https://uvacompute.com) +# To enable, set the UVA_RUNNER repo variable to the correct runner label. +# runs-on: ${{ vars.UVA_RUNNER || 'ubuntu-latest' }} + on: push: branches: [main] @@ -12,7 +16,7 @@ permissions: jobs: cargo: name: Cargo Build - runs-on: ${{ vars.UVA_RUNNER || 'ubuntu-latest' }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -39,7 +43,7 @@ jobs: docker: name: Docker Build - runs-on: ${{ vars.UVA_RUNNER || 'ubuntu-latest' }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From f05f441deea6e678adc6617c180f119617183526 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 15:16:42 -0400 Subject: [PATCH 02/64] gitignore and comment --- .gitignore | 1 + src/backend/annotate.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e1b69c2..f65d7b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ .humanlayer/ +tmp/ diff --git a/src/backend/annotate.rs b/src/backend/annotate.rs index d4f4fed..4a479c7 100644 --- a/src/backend/annotate.rs +++ b/src/backend/annotate.rs @@ -5,7 +5,7 @@ use imageproc::rect::Rect; use crate::core::types::WindowInfo; -// Embedded font - DejaVu Sans Mono for guaranteed availability +// Embedded font const FONT_BYTES: &[u8] = include_bytes!("../../assets/DejaVuSansMono.ttf"); const COLORS: &[Rgba] = &[ From 50c95947806e2957687a694db31c9ec7384faf0f Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 15:44:35 -0400 Subject: [PATCH 03/64] site init (barrett) --- site/.gitignore | 4 + site/.prettierrc | 11 + site/astro.config.mjs | 22 + site/package.json | 24 + site/pnpm-lock.yaml | 5105 ++++++++++++++++++++++++++++++ site/public/favicon.png | Bin 0 -> 4914 bytes site/public/favicon.svg | 14 + site/src/layouts/DocLayout.astro | 115 + site/src/pages/architecture.mdx | 19 + site/src/pages/index.astro | 35 + site/src/pages/installation.mdx | 45 + site/src/pages/usage.mdx | 53 + site/src/styles/base.css | 346 ++ site/src/themes.mjs | 59 + site/tsconfig.json | 10 + 15 files changed, 5862 insertions(+) create mode 100644 site/.gitignore create mode 100644 site/.prettierrc create mode 100644 site/astro.config.mjs create mode 100644 site/package.json create mode 100644 site/pnpm-lock.yaml create mode 100644 site/public/favicon.png create mode 100644 site/public/favicon.svg create mode 100644 site/src/layouts/DocLayout.astro create mode 100644 site/src/pages/architecture.mdx create mode 100644 site/src/pages/index.astro create mode 100644 site/src/pages/installation.mdx create mode 100644 site/src/pages/usage.mdx create mode 100644 site/src/styles/base.css create mode 100644 site/src/themes.mjs create mode 100644 site/tsconfig.json diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..71d18a2 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.astro/ +.vercel/ diff --git a/site/.prettierrc b/site/.prettierrc new file mode 100644 index 0000000..d87faa5 --- /dev/null +++ b/site/.prettierrc @@ -0,0 +1,11 @@ +{ + "plugins": ["prettier-plugin-astro"], + "overrides": [ + { + "files": "*.astro", + "options": { + "parser": "astro" + } + } + ] +} diff --git a/site/astro.config.mjs b/site/astro.config.mjs new file mode 100644 index 0000000..c1ae52c --- /dev/null +++ b/site/astro.config.mjs @@ -0,0 +1,22 @@ +import { defineConfig } from "astro/config"; +import mdx from "@astrojs/mdx"; +import vercel from "@astrojs/vercel"; +import { midnight, daylight } from "./src/themes.mjs"; + +export default defineConfig({ + output: "static", + adapter: vercel(), + build: { + format: "file", + }, + integrations: [mdx()], + markdown: { + shikiConfig: { + themes: { + light: daylight, + dark: midnight, + }, + wrap: true, + }, + }, +}); diff --git a/site/package.json b/site/package.json new file mode 100644 index 0000000..7842c9f --- /dev/null +++ b/site/package.json @@ -0,0 +1,24 @@ +{ + "name": "deskctl-site", + "type": "module", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "check": "astro check", + "format:check": "prettier --check 'src/**/*.{astro,mdx,css}' astro.config.mjs" + }, + "dependencies": { + "@astrojs/mdx": "^4.3.14", + "@astrojs/vercel": "^9.0.5", + "astro": "^5.18.1" + }, + "devDependencies": { + "@astrojs/check": "^0.9.8", + "prettier": "^3.8.1", + "prettier-plugin-astro": "^0.14.1", + "typescript": "^5.9.3" + } +} diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml new file mode 100644 index 0000000..bad2591 --- /dev/null +++ b/site/pnpm-lock.yaml @@ -0,0 +1,5105 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@astrojs/mdx': + specifier: ^4.3.14 + version: 4.3.14(astro@5.18.1(@vercel/functions@2.2.13)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3)) + '@astrojs/vercel': + specifier: ^9.0.5 + version: 9.0.5(astro@5.18.1(@vercel/functions@2.2.13)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3))(rollup@4.60.0) + astro: + specifier: ^5.18.1 + version: 5.18.1(@vercel/functions@2.2.13)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3) + devDependencies: + '@astrojs/check': + specifier: ^0.9.8 + version: 0.9.8(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3) + prettier: + specifier: ^3.8.1 + version: 3.8.1 + prettier-plugin-astro: + specifier: ^0.14.1 + version: 0.14.1 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + +packages: + + '@astrojs/check@0.9.8': + resolution: {integrity: sha512-LDng8446QLS5ToKjRHd3bgUdirvemVVExV7nRyJfW2wV36xuv7vDxwy5NWN9zqeSEDgg0Tv84sP+T3yEq+Zlkw==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + + '@astrojs/compiler@2.13.1': + resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} + + '@astrojs/internal-helpers@0.7.6': + resolution: {integrity: sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==} + + '@astrojs/language-server@2.16.6': + resolution: {integrity: sha512-N990lu+HSFiG57owR0XBkr02BYMgiLCshLf+4QG4v6jjSWkBeQGnzqi+E1L08xFPPJ7eEeXnxPXGLaVv5pa4Ug==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + + '@astrojs/markdown-remark@6.3.11': + resolution: {integrity: sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==} + + '@astrojs/mdx@4.3.14': + resolution: {integrity: sha512-FBrqJQORVm+rkRa2TS5CjU9PBA6hkhrwLVBSS9A77gN2+iehvjq1w6yya/d0YKC7osiVorKkr3Qd9wNbl0ZkGA==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + astro: ^5.0.0 + + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/vercel@9.0.5': + resolution: {integrity: sha512-rGuoVF/faLRcW1hE9Bu/DZPgaqk3TT6MzqiLsVemotecSSqZeSAh4+x7cNq7fLk75vS/dm14rO5V1lm+8Gp3dg==} + peerDependencies: + astro: ^5.0.0 + + '@astrojs/yaml2ts@0.2.3': + resolution: {integrity: sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg==} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@capsizecss/unpack@4.0.0': + resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} + engines: {node: '>=18'} + + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.1': + resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.4': + resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.4': + resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.4': + resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.4': + resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.4': + resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.4': + resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.4': + resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.4': + resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.4': + resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.4': + resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.4': + resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.4': + resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.4': + resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.4': + resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.4': + resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.4': + resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.4': + resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.4': + resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.4': + resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.4': + resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.4': + resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.4': + resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.4': + resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.4': + resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.4': + resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.4': + resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@mapbox/node-pre-gyp@2.0.3': + resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} + engines: {node: '>=18'} + hasBin: true + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.60.0': + resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.0': + resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.0': + resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.0': + resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.0': + resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.0': + resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.0': + resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.0': + resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.0': + resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.0': + resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.0': + resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.0': + resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.0': + resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.0': + resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.0': + resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.0': + resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.0': + resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.0': + resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.0': + resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.0': + resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.0': + resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.0': + resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.0': + resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.0': + resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==} + cpu: [x64] + os: [win32] + + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vercel/analytics@1.6.1': + resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} + peerDependencies: + '@remix-run/react': ^2 + '@sveltejs/kit': ^1 || ^2 + next: '>= 13' + react: ^18 || ^19 || ^19.0.0-rc + svelte: '>= 4' + vue: ^3 + vue-router: ^4 + peerDependenciesMeta: + '@remix-run/react': + optional: true + '@sveltejs/kit': + optional: true + next: + optional: true + react: + optional: true + svelte: + optional: true + vue: + optional: true + vue-router: + optional: true + + '@vercel/functions@2.2.13': + resolution: {integrity: sha512-14ArBSIIcOBx9nrEgaJb4Bw+en1gl6eSoJWh8qjifLl5G3E4dRXCFOT8HP+w66vb9Wqyd1lAQBrmRhRwOj9X9A==} + engines: {node: '>= 18'} + peerDependencies: + '@aws-sdk/credential-provider-web-identity': '*' + peerDependenciesMeta: + '@aws-sdk/credential-provider-web-identity': + optional: true + + '@vercel/nft@0.30.4': + resolution: {integrity: sha512-wE6eAGSXScra60N2l6jWvNtVK0m+sh873CpfZW4KI2v8EHuUQp+mSEi4T+IcdPCSEDgCdAS/7bizbhQlkjzrSA==} + engines: {node: '>=18'} + hasBin: true + + '@vercel/oidc@2.0.2': + resolution: {integrity: sha512-59PBFx3T+k5hLTEWa3ggiMpGRz1OVvl9eN8SUai+A43IsqiOuAe7qPBf+cray/Fj6mkgnxm/D7IAtjc8zSHi7g==} + engines: {node: '>= 18'} + + '@vercel/routing-utils@5.3.3': + resolution: {integrity: sha512-KYm2sLNUD48gDScv8ob4ejc3Gww2jcJyW80hTdYlenAPz/5BQar1Gyh38xrUuZ532TUwSb5mV1uRbAuiykq0EQ==} + + '@volar/kit@2.4.28': + resolution: {integrity: sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/language-server@2.4.28': + resolution: {integrity: sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==} + + '@volar/language-service@2.4.28': + resolution: {integrity: sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + astro@5.18.1: + resolution: {integrity: sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + + async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base-64@1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + deterministic-object-hash@2.0.2: + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} + + devalue@5.6.4: + resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.4: + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.4.1: + resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} + + fontkitten@1.0.3: + resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} + engines: {node: '>=20'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + h3@1.15.10: + resolution: {integrity: sha512-YzJeWSkDZxAhvmp8dexjRK5hxziRO7I9m0N53WhvYL5NiWfkUkzssVzY9jvGu0HBoLFW6+duYmNSn6MaZBCCtg==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.2.7: + resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} + engines: {node: 20 || >=22} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.5: + resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} + + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + + p-queue@8.1.1: + resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} + engines: {node: '>=18'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@6.1.0: + resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + prettier-plugin-astro@0.14.1: + resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==} + engines: {node: ^14.15.0 || >=16.0.0} + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + request-light@0.5.8: + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + + rollup@4.60.0: + resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + s.color@0.0.15: + resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} + + sass-formatter@0.7.9: + resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + smol-toml@1.6.1: + resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + suf-log@2.5.3: + resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} + + svgo@4.0.1: + resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} + engines: {node: '>=16'} + hasBin: true + + tar@7.5.13: + resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} + engines: {node: '>=18'} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.7.4: + resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + unstorage@1.17.4: + resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.2: + resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + volar-service-css@0.0.70: + resolution: {integrity: sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.70: + resolution: {integrity: sha512-xi5bC4m/VyE3zy/n2CXspKeDZs3qA41tHLTw275/7dNWM/RqE2z3BnDICQybHIVp/6G1iOQj5c1qXMgQC08TNg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.70: + resolution: {integrity: sha512-eR6vCgMdmYAo4n+gcT7DSyBQbwB8S3HZZvSagTf0sxNaD4WppMCFfpqWnkrlGStPKMZvMiejRRVmqsX9dYcTvQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.70: + resolution: {integrity: sha512-Z6BCFSpGVCd8BPAsZ785Kce1BGlWd5ODqmqZGVuB14MJvrR4+CYz6cDy4F+igmE1gMifqfvMhdgT8Aud4M5ngg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.70: + resolution: {integrity: sha512-IdD13Z9N2Bu8EM6CM0fDV1E69olEYGHDU25X51YXmq8Y0CmJ2LNj6gOiBJgpS5JGUqFzECVhMNBW7R0sPdRTMQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-typescript@0.0.70: + resolution: {integrity: sha512-l46Bx4cokkUedTd74ojO5H/zqHZJ8SUuyZ0IB8JN4jfRqUM3bQFBHoOwlZCyZmOeO0A3RQNkMnFclxO4c++gsg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-yaml@0.0.70: + resolution: {integrity: sha512-0c8bXDBeoATF9F6iPIlOuYTuZAC4c+yi0siQo920u7eiBJk8oQmUmg9cDUbR4+Gl++bvGP4plj3fErbJuPqdcQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.3.10: + resolution: {integrity: sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==} + + vscode-html-languageservice@5.6.2: + resolution: {integrity: sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==} + + vscode-json-languageservice@4.1.8: + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yaml-language-server@1.20.0: + resolution: {integrity: sha512-qhjK/bzSRZ6HtTvgeFvjNPJGWdZ0+x5NREV/9XZWFjIGezew2b4r5JPy66IfOhd5OA7KeFwk1JfmEbnTvev0cA==} + hasBin: true + + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} + engines: {node: '>= 14'} + hasBin: true + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yocto-spinner@0.2.3: + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod-to-ts@1.2.0: + resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} + peerDependencies: + typescript: ^4.9.4 || ^5.0.2 + zod: ^3 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@astrojs/check@0.9.8(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3)': + dependencies: + '@astrojs/language-server': 2.16.6(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3) + chokidar: 4.0.3 + kleur: 4.1.5 + typescript: 5.9.3 + yargs: 17.7.2 + transitivePeerDependencies: + - prettier + - prettier-plugin-astro + + '@astrojs/compiler@2.13.1': {} + + '@astrojs/internal-helpers@0.7.6': {} + + '@astrojs/language-server@2.16.6(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3)': + dependencies: + '@astrojs/compiler': 2.13.1 + '@astrojs/yaml2ts': 0.2.3 + '@jridgewell/sourcemap-codec': 1.5.5 + '@volar/kit': 2.4.28(typescript@5.9.3) + '@volar/language-core': 2.4.28 + '@volar/language-server': 2.4.28 + '@volar/language-service': 2.4.28 + muggle-string: 0.4.1 + tinyglobby: 0.2.15 + volar-service-css: 0.0.70(@volar/language-service@2.4.28) + volar-service-emmet: 0.0.70(@volar/language-service@2.4.28) + volar-service-html: 0.0.70(@volar/language-service@2.4.28) + volar-service-prettier: 0.0.70(@volar/language-service@2.4.28)(prettier@3.8.1) + volar-service-typescript: 0.0.70(@volar/language-service@2.4.28) + volar-service-typescript-twoslash-queries: 0.0.70(@volar/language-service@2.4.28) + volar-service-yaml: 0.0.70(@volar/language-service@2.4.28) + vscode-html-languageservice: 5.6.2 + vscode-uri: 3.1.0 + optionalDependencies: + prettier: 3.8.1 + prettier-plugin-astro: 0.14.1 + transitivePeerDependencies: + - typescript + + '@astrojs/markdown-remark@6.3.11': + dependencies: + '@astrojs/internal-helpers': 0.7.6 + '@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.23.0 + smol-toml: 1.6.1 + 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 + transitivePeerDependencies: + - supports-color + + '@astrojs/mdx@4.3.14(astro@5.18.1(@vercel/functions@2.2.13)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3))': + dependencies: + '@astrojs/markdown-remark': 6.3.11 + '@mdx-js/mdx': 3.1.1 + acorn: 8.16.0 + astro: 5.18.1(@vercel/functions@2.2.13)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3) + 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.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@3.3.0': + dependencies: + prismjs: 1.30.0 + + '@astrojs/telemetry@3.3.0': + dependencies: + ci-info: 4.4.0 + debug: 4.4.3 + dlv: 1.1.3 + dset: 3.1.4 + is-docker: 3.0.0 + is-wsl: 3.1.1 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@astrojs/vercel@9.0.5(astro@5.18.1(@vercel/functions@2.2.13)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3))(rollup@4.60.0)': + dependencies: + '@astrojs/internal-helpers': 0.7.6 + '@vercel/analytics': 1.6.1 + '@vercel/functions': 2.2.13 + '@vercel/nft': 0.30.4(rollup@4.60.0) + '@vercel/routing-utils': 5.3.3 + astro: 5.18.1(@vercel/functions@2.2.13)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3) + esbuild: 0.25.12 + tinyglobby: 0.2.15 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-web-identity' + - '@remix-run/react' + - '@sveltejs/kit' + - encoding + - next + - react + - rollup + - supports-color + - svelte + - vue + - vue-router + + '@astrojs/yaml2ts@0.2.3': + dependencies: + yaml: 2.8.3 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@capsizecss/unpack@4.0.0': + dependencies: + fontkitten: 1.0.3 + + '@emmetio/abbreviation@2.3.3': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-abbreviation@2.1.8': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-parser@0.4.1': + dependencies: + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 + + '@emmetio/html-matcher@1.3.0': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/scanner@1.0.4': {} + + '@emmetio/stream-reader-utils@0.1.0': {} + + '@emmetio/stream-reader@2.2.0': {} + + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.4': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.4': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.4': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.4': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.4': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.4': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.4': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.4': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.4': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.4': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.4': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.4': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.4': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.4': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.4': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.4': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.4': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.4': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.4': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.4': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.4': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.4': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.4': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.4': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.4': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.4': + optional: true + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.9.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@mapbox/node-pre-gyp@2.0.3': + dependencies: + consola: 3.4.2 + detect-libc: 2.1.2 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0 + nopt: 8.1.0 + semver: 7.7.4 + tar: 7.5.13 + transitivePeerDependencies: + - encoding + - supports-color + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.16.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.16.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@oslojs/encoding@1.1.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/pluginutils@5.3.0(rollup@4.60.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.60.0 + + '@rollup/rollup-android-arm-eabi@4.60.0': + optional: true + + '@rollup/rollup-android-arm64@4.60.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.0': + optional: true + + '@rollup/rollup-darwin-x64@4.60.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.0': + optional: true + + '@shikijs/core@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.5 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vercel/analytics@1.6.1': {} + + '@vercel/functions@2.2.13': + dependencies: + '@vercel/oidc': 2.0.2 + + '@vercel/nft@0.30.4(rollup@4.60.0)': + dependencies: + '@mapbox/node-pre-gyp': 2.0.3 + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 10.5.0 + graceful-fs: 4.2.11 + node-gyp-build: 4.8.4 + picomatch: 4.0.4 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/oidc@2.0.2': + dependencies: + '@types/ms': 2.1.0 + ms: 2.1.3 + + '@vercel/routing-utils@5.3.3': + dependencies: + path-to-regexp: 6.1.0 + path-to-regexp-updated: path-to-regexp@6.3.0 + optionalDependencies: + ajv: 6.14.0 + + '@volar/kit@2.4.28(typescript@5.9.3)': + dependencies: + '@volar/language-service': 2.4.28 + '@volar/typescript': 2.4.28 + typesafe-path: 0.2.2 + typescript: 5.9.3 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/language-server@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + '@volar/language-service': 2.4.28 + '@volar/typescript': 2.4.28 + path-browserify: 1.0.1 + request-light: 0.7.0 + vscode-languageserver: 9.0.1 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-service@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vscode/emmet-helper@2.11.0': + dependencies: + emmet: 2.4.11 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + '@vscode/l10n@0.0.18': {} + + abbrev@3.0.1: {} + + acorn-import-attributes@1.9.5(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + agent-base@7.1.4: {} + + ajv-draft-04@1.0.0(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + optional: true + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + astring@1.9.0: {} + + astro@5.18.1(@vercel/functions@2.2.13)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3): + dependencies: + '@astrojs/compiler': 2.13.1 + '@astrojs/internal-helpers': 0.7.6 + '@astrojs/markdown-remark': 6.3.11 + '@astrojs/telemetry': 3.3.0 + '@capsizecss/unpack': 4.0.0 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) + acorn: 8.16.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.4.0 + 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.4 + diff: 8.0.4 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.27.4 + estree-walker: 3.0.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 + import-meta-resolve: 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 + p-limit: 6.2.0 + p-queue: 8.1.1 + package-manager-detector: 1.6.0 + piccolore: 0.1.3 + picomatch: 4.0.4 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.4 + shiki: 3.23.0 + smol-toml: 1.6.1 + svgo: 4.0.1 + tinyexec: 1.0.4 + tinyglobby: 0.2.15 + tsconfck: 3.1.6(typescript@5.9.3) + ultrahtml: 1.6.0 + unifont: 0.7.4 + unist-util-visit: 5.1.0 + unstorage: 1.17.4(@vercel/functions@2.2.13) + vfile: 6.0.3 + vite: 6.4.1(yaml@2.8.3) + vitefu: 1.1.2(vite@6.4.1(yaml@2.8.3)) + 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@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + + async-sema@3.1.1: {} + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + base-64@1.0.0: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + boolbase@1.0.0: {} + + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.6.2 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + camelcase@8.0.0: {} + + ccount@2.0.1: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + chownr@3.0.0: {} + + ci-info@4.4.0: {} + + cli-boxes@3.0.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + + common-ancestor-path@1.0.1: {} + + consola@3.4.2: {} + + cookie-es@1.2.2: {} + + cookie@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + deterministic-object-hash@2.0.2: + dependencies: + base-64: 1.0.0 + + devalue@5.6.4: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.4: {} + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dset@3.1.4: {} + + eastasianwidth@0.2.0: {} + + emmet@2.4.11: + dependencies: + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.16.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + 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 + + esbuild@0.27.4: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.4 + '@esbuild/android-arm': 0.27.4 + '@esbuild/android-arm64': 0.27.4 + '@esbuild/android-x64': 0.27.4 + '@esbuild/darwin-arm64': 0.27.4 + '@esbuild/darwin-x64': 0.27.4 + '@esbuild/freebsd-arm64': 0.27.4 + '@esbuild/freebsd-x64': 0.27.4 + '@esbuild/linux-arm': 0.27.4 + '@esbuild/linux-arm64': 0.27.4 + '@esbuild/linux-ia32': 0.27.4 + '@esbuild/linux-loong64': 0.27.4 + '@esbuild/linux-mips64el': 0.27.4 + '@esbuild/linux-ppc64': 0.27.4 + '@esbuild/linux-riscv64': 0.27.4 + '@esbuild/linux-s390x': 0.27.4 + '@esbuild/linux-x64': 0.27.4 + '@esbuild/netbsd-arm64': 0.27.4 + '@esbuild/netbsd-x64': 0.27.4 + '@esbuild/openbsd-arm64': 0.27.4 + '@esbuild/openbsd-x64': 0.27.4 + '@esbuild/openharmony-arm64': 0.27.4 + '@esbuild/sunos-x64': 0.27.4 + '@esbuild/win32-arm64': 0.27.4 + '@esbuild/win32-ia32': 0.27.4 + '@esbuild/win32-x64': 0.27.4 + + escalade@3.2.0: {} + + escape-string-regexp@5.0.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + eventemitter3@5.0.4: {} + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: + optional: true + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-uri-to-path@1.0.0: {} + + flattie@1.1.1: {} + + fontace@0.4.1: + dependencies: + fontkitten: 1.0.3 + + fontkitten@1.0.3: + dependencies: + tiny-inflate: 1.0.3 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + + github-slugger@2.0.0: {} + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + graceful-fs@4.2.11: {} + + h3@1.15.10: + 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 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + import-meta-resolve@4.2.0: {} + + inline-style-parser@0.2.7: {} + + iron-webcrypto@1.2.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-docker@3.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-hexadecimal@2.0.1: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-plain-obj@4.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-schema-traverse@0.4.1: + optional: true + + json-schema-traverse@1.0.0: {} + + jsonc-parser@2.3.1: {} + + jsonc-parser@3.3.1: {} + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + longest-streak@3.1.0: {} + + lru-cache@10.4.3: {} + + lru-cache@11.2.7: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.0.28: {} + + mdn-data@2.27.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.7: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build@4.8.4: {} + + node-mock-http@1.0.4: {} + + nopt@8.1.0: + dependencies: + abbrev: 3.0.1 + + normalize-path@3.0.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.3 + + ohash@2.0.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.5: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.2 + + p-queue@8.1.1: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 6.1.4 + + p-timeout@6.1.4: {} + + package-json-from-dist@1.0.1: {} + + package-manager-detector@1.6.0: {} + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-browserify@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-to-regexp@6.1.0: {} + + path-to-regexp@6.3.0: {} + + piccolore@0.1.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier-plugin-astro@0.14.1: + dependencies: + '@astrojs/compiler': 2.13.1 + prettier: 3.8.1 + sass-formatter: 0.7.9 + + prettier@3.8.1: {} + + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + property-information@7.1.0: {} + + punycode@2.3.1: + optional: true + + radix3@1.1.2: {} + + readdirp@4.1.2: {} + + readdirp@5.0.0: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + request-light@0.5.8: {} + + request-light@0.7.0: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@5.0.0: {} + + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.1.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + + rollup@4.60.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.0 + '@rollup/rollup-android-arm64': 4.60.0 + '@rollup/rollup-darwin-arm64': 4.60.0 + '@rollup/rollup-darwin-x64': 4.60.0 + '@rollup/rollup-freebsd-arm64': 4.60.0 + '@rollup/rollup-freebsd-x64': 4.60.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.0 + '@rollup/rollup-linux-arm-musleabihf': 4.60.0 + '@rollup/rollup-linux-arm64-gnu': 4.60.0 + '@rollup/rollup-linux-arm64-musl': 4.60.0 + '@rollup/rollup-linux-loong64-gnu': 4.60.0 + '@rollup/rollup-linux-loong64-musl': 4.60.0 + '@rollup/rollup-linux-ppc64-gnu': 4.60.0 + '@rollup/rollup-linux-ppc64-musl': 4.60.0 + '@rollup/rollup-linux-riscv64-gnu': 4.60.0 + '@rollup/rollup-linux-riscv64-musl': 4.60.0 + '@rollup/rollup-linux-s390x-gnu': 4.60.0 + '@rollup/rollup-linux-x64-gnu': 4.60.0 + '@rollup/rollup-linux-x64-musl': 4.60.0 + '@rollup/rollup-openbsd-x64': 4.60.0 + '@rollup/rollup-openharmony-arm64': 4.60.0 + '@rollup/rollup-win32-arm64-msvc': 4.60.0 + '@rollup/rollup-win32-ia32-msvc': 4.60.0 + '@rollup/rollup-win32-x64-gnu': 4.60.0 + '@rollup/rollup-win32-x64-msvc': 4.60.0 + fsevents: 2.3.3 + + s.color@0.0.15: {} + + sass-formatter@0.7.9: + dependencies: + suf-log: 2.5.3 + + sax@1.6.0: {} + + semver@7.7.4: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@3.23.0: + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + smol-toml@1.6.1: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + suf-log@2.5.3: + dependencies: + s.color: 0.0.15 + + svgo@4.0.1: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.2.1 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.6.0 + + tar@7.5.13: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + tiny-inflate@1.0.3: {} + + tinyexec@1.0.4: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tr46@0.0.3: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tsconfck@3.1.6(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + tslib@2.8.1: + optional: true + + type-fest@4.41.0: {} + + typesafe-path@0.2.2: {} + + typescript-auto-import-cache@0.3.6: + dependencies: + semver: 7.7.4 + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.7.4: + dependencies: + css-tree: 3.2.1 + ofetch: 1.5.1 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unstorage@1.17.4(@vercel/functions@2.2.13): + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.10 + lru-cache: 11.2.7 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.3 + optionalDependencies: + '@vercel/functions': 2.2.13 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + optional: true + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@6.4.1(yaml@2.8.3): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.8 + rollup: 4.60.0 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + yaml: 2.8.3 + + vitefu@1.1.2(vite@6.4.1(yaml@2.8.3)): + optionalDependencies: + vite: 6.4.1(yaml@2.8.3) + + volar-service-css@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-css-languageservice: 6.3.10 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-emmet@0.0.70(@volar/language-service@2.4.28): + dependencies: + '@emmetio/css-parser': 0.4.1 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-html@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-html-languageservice: 5.6.2 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-prettier@0.0.70(@volar/language-service@2.4.28)(prettier@3.8.1): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + prettier: 3.8.1 + + volar-service-typescript-twoslash-queries@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-typescript@0.0.70(@volar/language-service@2.4.28): + dependencies: + path-browserify: 1.0.1 + semver: 7.7.4 + typescript-auto-import-cache: 0.3.6 + vscode-languageserver-textdocument: 1.0.12 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-yaml@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-uri: 3.1.0 + yaml-language-server: 1.20.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + vscode-css-languageservice@6.3.10: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-html-languageservice@5.6.2: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-json-languageservice@4.1.8: + dependencies: + jsonc-parser: 3.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-nls@5.2.0: {} + + vscode-uri@3.1.0: {} + + web-namespaces@2.0.1: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-pm-runs@1.1.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xxhash-wasm@1.1.0: {} + + y18n@5.0.8: {} + + yallist@5.0.0: {} + + yaml-language-server@1.20.0: + dependencies: + '@vscode/l10n': 0.0.18 + ajv: 8.18.0 + ajv-draft-04: 1.0.0(ajv@8.18.0) + prettier: 3.8.1 + request-light: 0.5.8 + vscode-json-languageservice: 4.1.8 + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + yaml: 2.7.1 + + yaml@2.7.1: {} + + yaml@2.8.3: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@1.2.2: {} + + yocto-spinner@0.2.3: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.1(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): + dependencies: + typescript: 5.9.3 + zod: 3.25.76 + + zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/site/public/favicon.png b/site/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a2c7383d3056ce9c395739620ae4eca154ab4132 GIT binary patch literal 4914 zcmeAS@N?(olHy`uVBq!ia0y~yV0Z|^9Lx+144k=29t;eO+yOozuK)l4KXc{`;|ya4 z28M=)28LJ`*MCgs&!6A3XAfho_=yuIZr!?-_HRaOYina;Bjaq7xpU{PU%&qQ_wNiT zO6BF{jO7{+A3p5r>N;@Xz}Br>OG-*4BqSIcC9?j{TfTfbLo|btk&(T<{nV*b877*V zJP?2L=1oLIL}FrMaBwi=BwYq)vFz+@h5%`AZ*O^dc|AQn22FN`d{v7VLJUTn0s;aO z9UM_VE0h}<8Diy)_c8_?FOPWFsXWJ)A&iB=i`o1PBV&_}+qw(}B}N7ZW`-gO1`i1a zSw;pOM!h0elO{itgNzI&f`(fd8DuyaMA#X`*rnIGTi;;NTgIS0ok1p@fzOn|XICME zA~!?0fam%X5)2H2k33x*Ln`Lny_;Em`NM`Xn2}@QS2f%2aob}Pj#s`t zjbGJ{9he;@oczH5{=cYXC1$(d-vVnp8Sec1c5!k2wJ9eWeIhRFa4B%eJBS@#w_V_U zg-)fJ1(VAXyq;aR~4T~^~*8N+eEL|!Ktv{9AI;quv<2#Rt%e*6rtEal>{AM+K z@Jftj^_&0my%KH|_FLYWqL(V98sILYa^Tzz+lHcb=S79jJ^pX@Jn^#Sp{IVyMp2xJ zTvr4fExP|3%-{L{$L!NTddv7ewSEj2_{DJS$WIF?4n?-K&Y&AJe_G@h?=kKYYdzE_ zo*)yro8O6bhN9QQ8|ORIyT2v$UN~Jj>ug>$M}6i>#}4b@^M^7n9}>^v3Vl>4+d3t} zJFzXoexcWzg$AnswyrzW@*-TK@AhmVv+JCy7grm|xM#N-+lO|4NLU*nAe?ypaO88t zg1G@J<)rplcQ$A2ogln)!^Sta=0>n?`^{>azhc&TcW=qXsV|LN9}9MA{?__EJ-fw$ zMX}^e>9WilSNc>7CSK-ixh1^%&G!fQH+Jy&iS$jH{!zk#tGl&_!|7V)RSxk_Tf5_a zt~dM3BFpmRy+<-9dzn;kAd`&gss9>>CyGhb#B20)#3@ZD#-jyWpPtu3{RK zB!m|;_Ge@krA|H6_{QMjf$K*P%=qzj{RjT_YIn`*A1ZWtPue(vsrZ+cMd8i|h394+ zK6dEfPgZ`unw@+nI`%|uwh~=`=YHl4?!%I7aW5Ntk2y9hPFJ-#nE$cRc7ovk({rwe zLo*-7EwI)<|?B0HjOOb)Z>@9H~r zyI*JCG!~|hXY2nvI4CFHc)C1~*+7G>t3cFF(rF5-krK~t)dR|tqgJte`nbACcKy3U zpDYCT3qDwp^x#?R;pNv3JYzU#ZNQWl^MdKkXP!7IMswW~hBg_C2M6YEI>5`_zCPAC zgXhkg3CzxguQ~(SmhaK3Y0Y2C@At#CVYbLF2L6iXf>gdc3ob|-=zOYiT*#&K-n-#9 zL(VKlbG0W9KVQl;+*LX&F2Xne2D7__g)wWMhesoCb;3oH_4=A$COF!qr|0qA=?FOa z;1bJ@BL_CJ76?1>`K%N8c3>%Q0rR1Uy~YC9%vqGR6TUi$KagPNf78Hss7`Ys`-6?b z7hEf1oL=x+Y-J78DPin-xK!9ZVXG6L&niXl=81t^Ik%a$Mfu``5-Q_)Vk(%zG(#HI zB;PD?-s8BTfH6$t3!64gW6`{BOfL)WVKbuw_K3^a6>R+YQc|rvOc%0;sqim zUSK`Ju6{&^<*mYBW&t~u1XU-|45RN}Jo<$XRiO=WPA1gHn!#cGzMVUI?j{BCvwOsA;dl}m~FIslXwfZ?o)J{yk zk<&JHyUs7+U#v=tlEwCNiat2QcH;Pkr#>h6|=l zhdxZ2)7tPn=(mDj*hiHV$Imn5XGlo zm{2xvRkTdfz8=dnKie8PO!ZRJP!&);bkk(lN4bLwGl zSBGk)ln*zzZ5C)a`C*OdheZMz-cKa&l!>ujWq#YC)Lhpx?SLhlSikcu;Z(N09Ks3b z*(#!#GW>juUO2yCuGn1W)-Gvy_QAH#Td#=SFl1JGRDVrau;BN5f7#ZPBAkn-PI~4r zSw|@0bYKSOoQIiWs@J|srcPuy-h8(eru4?BJPW!%Kd$L*J-t4tAqh`BC zx!i5}JAK)c*JGg-3G6Zo z2`i5tW;>F@7TYp0;(B4qw!Z745iVC!E@-xg9oT1VFn`7A1WA-u+wZI}{wE6HScSK3x>I<9--?`k}i>OuKE}HsbL3Y_h!2_qe8h`2BdU`>p`Scr| zmaK-TMN&O`WdE-}242d&B7X^!SB843l-jlzZ-fl3+Z( zZojkif-^~?hbFvz)ICdH@waT%=>-QT1)WGzbu!!5o%Q_9uH^d%&o)GdIZSl@e`tcQ zwVk_WO8>@9Hyn((Yf|@U%t+nXB(v!j&#X$JO}^dyZ-X-;T_)??s}|oh?UJ6}lI4Eo z&T|~QT@Tjq99+HO{5owxF{``UAM!6`T`hhReItMK?1u}cGx^896YZ1q+4RC!;@fTJ zif=bUuH4vlHhH?1G}|pbuDo9&hhB6`JUp6s``|Oh<}ht9u7-(W8MnWEo#?p!kX9pK zwDu3-PZz~4xgVAaZDaOLkACD<@qC-X{n-HwPjy;M-qdJbSlLk}BDCPN4vXYGqZhs> z+~fZ+77CW$kvZHSz@z{1*WtC*OtwsaIg%A#|LApL^E|u!+ZOWDCUbEem{SeBm3@l|M@B(->qfJnRL7%d|z;p*bV{VS)~V8FR2MueXGb= z7~XTZX3Fw_JV@_+$4kU;4R*!rvwBuM(>t z+<6j|r9I{K`Ad5R7TmiU$d!4^m3iy5EfZeJRrKHA_d=s5bt+pF*HPwsN5UBY^cBe8 z7w+bM8OrtMjB(?(b+7e~dp(fNsSbDheJP5w)FI4c`OmpOw@herG`l2te8YU(SzL)a zrypnvggI^e$CLk!p;crlW8Tdb6Yf1Q@>#8;bm{uHD=W30)Nv+9E}b_kU0=jum$=iP zz4Uqhkq3P z|LIo7{O99w*~-Eba(0&fv%VH2zPwa!R>{}WD!@FscSGETe#ZGm+b8rrTrK$V@wIE# zznph3Si6`z$ZEll**TAr^kgy`)+*|zf6$!jv}?Xv=hjtERv5f4`DJN0D~#=i`>W;i zCzyULJpF3Nho7t7Ic+@PcTD;6J z^E6t&7;7z=@1ZEGQgctgs#}{m;P&C({Xf<-PdQY)SdS+-QBHtMGp^ZDeTK%?MuT;) z?(zjI%;poi=+u=pcXLA++v98B78osLn76_q&ZCg)C5v#J$7Jo7K4JxoHPRELUUTX$ zd9HFW;LzE5d(sp{4tvkqD{!Ng<*Z92*XTdsKu-Ql_2 z=(+Z`j|4M&mBJS34>Ai6Ii33Po#RJejctLdboGUX*Q*f&+lZ&}j*uulj z-?sdjeQg(S%R?ji87~)T?wd4klhZF{#%1PGD=sWNzWVD6*IQez#m1d*ix+xuH?Etj zewnSog+0#RGN!Flm&4nixNF2Ub{bvTlRX(Y*{fApk6k4a; zeRpr!fvXJ?Rntp+zI0tt>B+vE+N#tnb7FedgVt?X?7X{E_bzJ{G3|{gP1I(5XZEdd zs%%{MEH`V$xw$nu#}?ag-~16d>);!w=&jAmVnaM`zI~_1>eshozwcy|MXgirTJ_9d zus!ZXW76jK2eZ#;UGZT*E4ou`qyB>__uk#}dhm70!GH70>NYU-P2S@FTmOc=*}aak zwFYyT1MGs8RUgSbJcn*xp4NQpWSL){ zbAY5ttw#CA0H(yv?Xe+KosR6ye<91w_42s5eqMm0?8V!6-^Ny*?_Te9eai&7#qv^X zwGXCdTr989D^Hsv$-Le(mQ#>!sOoxt~=ZOK(hz3`9L>|(+D8aeicZ`SN;He<@l@>1$%e7oSG^X$D-Upz}_{W8nv z+sn6WE=bs9TYfolF>}^Kfptz#j@ovs*(slTpL;9J$#3(8FjmFxH51NV-Rbwi@`vuk zyD5uU`Mz2Emc^tr~WCb`v+R^un7B=`k+7aV7yyXPi#FOzb=Vttt1! zzEek*?of4_8zTIPw{&qw=H8N}^&2D8SE?9!?v`J-?7EVV`(+-8ww^ z(e~}tAtzNf*Vy$obtU}U5^1=Bt@hzekNrkx9WrnIv)y$~W^27ox!=Ty;LE8;>?`-| zt!z@7X>Mg>bLY|RzP**l3*6mH?9a)X7Pv<}Q?SjExVf;n!_<^x$D*za=Y#yKu14J7 z+s|%x%RQ}$efHyx+twD%$V%Y!mAZ`<-RD&=9FeD+L7 zu}MGn%#%z}c2W6g$cJp=L R7#J8BJYD@<);T3K0RW<2{Fnd$ literal 0 HcmV?d00001 diff --git a/site/public/favicon.svg b/site/public/favicon.svg new file mode 100644 index 0000000..884624f --- /dev/null +++ b/site/public/favicon.svg @@ -0,0 +1,14 @@ + + + :h + diff --git a/site/src/layouts/DocLayout.astro b/site/src/layouts/DocLayout.astro new file mode 100644 index 0000000..f2608de --- /dev/null +++ b/site/src/layouts/DocLayout.astro @@ -0,0 +1,115 @@ +--- +import "../styles/base.css"; +import type { MarkdownHeading } from "astro"; +const fm = Astro.props.frontmatter || Astro.props; +const title = fm.title; +const description = fm.description || "desktop control for AI agents"; +const toc = fm.toc ?? false; +const headings: MarkdownHeading[] = Astro.props.headings ?? []; +const isIndex = + Astro.url.pathname === "/" || Astro.url.pathname === "/index.html"; +const h2s = headings.filter((h) => h.depth === 2); + +function formatTocText(text: string): string { + if (!text.includes(" ") && /[-.]/.test(text)) { + return `${text}`; + } + return text.replace(/\(([^)]+)\)/g, "($1)"); +} +--- + + + + + + + + + {title ? `${title} — deskctl` : "deskctl"} + + + { + !isIndex && ( + + ) + } + + { + toc && ( + + ) + } + +
+ +
+ + + + diff --git a/site/src/pages/architecture.mdx b/site/src/pages/architecture.mdx new file mode 100644 index 0000000..064f995 --- /dev/null +++ b/site/src/pages/architecture.mdx @@ -0,0 +1,19 @@ +--- +layout: ../layouts/DocLayout.astro +title: Architecture +toc: true +--- + +# Architecture + +## Client-daemon model + +deskctl uses a client-daemon architecture over Unix sockets with an NDJSON wire protocol. The daemon starts automatically on the first command and keeps the X11 connection alive for fast repeated calls. + +## Backend design + +The backend is trait-based, making it straightforward to add support for different display servers. The current implementation targets X11 via `x11rb`. + +## Wayland support + +Coming soon. The trait-based backend design means adding Hyprland/Wayland support is a single trait implementation with zero refactoring of the core. diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro new file mode 100644 index 0000000..4858a1d --- /dev/null +++ b/site/src/pages/index.astro @@ -0,0 +1,35 @@ +--- +import DocLayout from "../layouts/DocLayout.astro"; +--- + + +
+

deskctl

+ desktop control CLI for AI agents +
+ +

+ X11 desktop control CLI for AI agents on Linux. Snapshot, click, type, and + focus windows through a simple command-line interface with a client-daemon + architecture over Unix sockets. +

+ +

Documentation

+ +
+ +

Links

+ + + diff --git a/site/src/pages/installation.mdx b/site/src/pages/installation.mdx new file mode 100644 index 0000000..faeca27 --- /dev/null +++ b/site/src/pages/installation.mdx @@ -0,0 +1,45 @@ +--- +layout: ../layouts/DocLayout.astro +title: Installation +--- + +# Installation + +## Cargo + +```sh +cargo install deskctl +``` + +## Docker build + +Build a Linux binary with Docker: + +```sh +docker compose -f docker/docker-compose.yml run --rm build +``` + +This writes `dist/deskctl-linux-x86_64`. + +## From source + +```sh +git clone https://github.com/harivansh-afk/deskctl +cd deskctl +cargo build +``` + +## Deploy to a remote machine + +Copy the binary to an SSH machine: + +```sh +ssh -p 443 deskctl@ssh.agentcomputer.ai 'cat > ~/deskctl && chmod +x ~/deskctl' < dist/deskctl-linux-x86_64 +``` + +## Runtime requirements + +- Linux with X11 session +- `DISPLAY` environment variable set +- `XDG_SESSION_TYPE=x11` +- A window manager exposing standard EWMH properties diff --git a/site/src/pages/usage.mdx b/site/src/pages/usage.mdx new file mode 100644 index 0000000..43118f6 --- /dev/null +++ b/site/src/pages/usage.mdx @@ -0,0 +1,53 @@ +--- +layout: ../layouts/DocLayout.astro +title: Usage +toc: true +--- + +# Usage + +## Snapshot + +Capture the current desktop state: + +```sh +deskctl snapshot +``` + +With annotations overlaid on windows: + +```sh +deskctl --json snapshot --annotate +``` + +## Click + +Click a window by its annotation handle: + +```sh +deskctl click @w1 +``` + +## Type + +Type text into the focused window: + +```sh +deskctl type "hello world" +``` + +## Focus + +Focus a window by name: + +```sh +deskctl focus "firefox" +``` + +## JSON output + +Pass `--json` for machine-readable output, useful for agent integrations: + +```sh +deskctl --json snapshot +``` diff --git a/site/src/styles/base.css b/site/src/styles/base.css new file mode 100644 index 0000000..f1bd0f4 --- /dev/null +++ b/site/src/styles/base.css @@ -0,0 +1,346 @@ +@font-face { + font-family: "Signifier"; + src: url("https://www.barrettruth.com/fonts/signifier/Signifier-Regular.ttf") + format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Signifier"; + src: url("https://www.barrettruth.com/fonts/signifier/Signifier-RegularItalic.ttf") + format("truetype"); + font-weight: 400; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "Signifier"; + src: url("https://www.barrettruth.com/fonts/signifier/Signifier-Light.ttf") + format("truetype"); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Signifier"; + src: url("https://www.barrettruth.com/fonts/signifier/Signifier-LightItalic.ttf") + format("truetype"); + font-weight: 300; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "Signifier"; + src: url("https://www.barrettruth.com/fonts/signifier/Signifier-Medium.ttf") + format("truetype"); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Signifier"; + src: url("https://www.barrettruth.com/fonts/signifier/Signifier-Bold.ttf") + format("truetype"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Berkeley Mono"; + src: url("https://www.barrettruth.com/fonts/berkeley-mono/BerkeleyMono-Regular.ttf") + format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Berkeley Mono"; + src: url("https://www.barrettruth.com/fonts/berkeley-mono/BerkeleyMono-Bold.ttf") + format("truetype"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Berkeley Mono"; + src: url("https://www.barrettruth.com/fonts/berkeley-mono/BerkeleyMono-Italic.ttf") + format("truetype"); + font-weight: 400; + font-style: italic; + font-display: swap; +} + +*, +*::before, +*::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + font-family: "Signifier", serif; + line-height: 1.65; + color: #1a1a1a; + background: #f5f5f5; +} + +@media (prefers-color-scheme: dark) { + html, + body { + background: #121212; + color: #e0e0e0; + } +} + +main { + max-width: 50rem; + margin: 0 auto; + padding: 3rem clamp(1.25rem, 5vw, 3rem) 6rem; +} + +.tagline { + font-size: 1.1rem; + opacity: 0.5; + margin-top: -0.75rem; + margin-bottom: 1.75rem; + font-style: italic; +} + +header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: nowrap; + gap: 20px; + margin-bottom: 1.5rem; +} + +header h1 { + margin-bottom: 0; +} + +header code { + font-size: clamp(1.75rem, 5vw, 2.5rem); +} + +h1 { + font-weight: 300; + font-size: clamp(1.75rem, 5vw, 2.5rem); + line-height: 1.2; + margin-bottom: 1.5rem; +} + +h2 { + font-weight: 400; + font-size: 1.35rem; + margin-top: 2.5rem; + margin-bottom: 0.75rem; +} + +h3 { + font-weight: 500; + font-size: 1.1rem; + margin-top: 2rem; + margin-bottom: 0.5rem; +} + +p { + margin-bottom: 1rem; +} + +a { + color: inherit; + text-decoration-thickness: 1px; + text-underline-offset: 0.15em; +} + +a:hover { + text-decoration-thickness: 2px; +} + +ul, +ol { + padding-left: 1.25em; + margin-bottom: 1rem; +} + +li { + margin-bottom: 0.35rem; +} + +pre, +code, +.astro-code { + font-family: "Berkeley Mono", monospace !important; + font-variant-ligatures: none; +} + +code { + font-size: 0.88em; + padding: 0.15em 0.35em; + border-radius: 3px; + background: #e8e8e8; +} + +@media (prefers-color-scheme: dark) { + code { + background: #222222; + } +} + +pre { + padding: 1rem 1.25rem; + border-radius: 4px; + overflow-x: auto; + white-space: pre; + word-wrap: normal; + margin-bottom: 1.25rem; + line-height: 1.5; + background: #ebebeb; +} + +@media (prefers-color-scheme: dark) { + pre { + background: #222222; + } +} + +pre code { + padding: 0; + background: none; + font-size: 0.85em; +} + +@media (prefers-color-scheme: dark) { + .astro-code, + .astro-code span { + color: var(--shiki-dark) !important; + background-color: var(--shiki-dark-bg) !important; + } +} + +table { + width: 100%; + border-collapse: collapse; + margin-bottom: 1.25rem; + font-size: 0.95em; +} + +th, +td { + text-align: left; + padding: 0.5rem 0.75rem; + border-bottom: 1px solid #d0d0d0; +} + +@media (prefers-color-scheme: dark) { + th, + td { + border-bottom-color: #2d2d2d; + } +} + +th { + font-weight: 500; +} + +hr { + border: none; + border-top: 1px solid #d0d0d0; + margin: 2.5rem 0; +} + +@media (prefers-color-scheme: dark) { + hr { + border-top-color: #2d2d2d; + } +} + +nav { + max-width: 50rem; + margin: 0 auto; + padding: 1.5rem clamp(1.25rem, 5vw, 3rem) 0; + font-size: 0.9rem; +} + +nav a { + color: inherit; + text-decoration: none; + opacity: 0.6; + transition: opacity 0.15s; +} + +nav a:hover { + opacity: 1; +} + +nav .title { + font-weight: 500; + opacity: 1; +} + +nav .sep { + opacity: 0.3; + margin: 0 0.5em; +} + +.toc-nav { + position: fixed; + top: 6.5rem; + left: max(1rem, calc(50vw - 25rem - 2rem - 11rem)); + width: 11rem; + font-size: 0.9rem; + line-height: 1.5; +} + +.toc-nav code { + font-size: 0.85em; + padding: 0.1em 0.25em; + border-radius: 2px; + background: #e8e8e8; +} + +@media (prefers-color-scheme: dark) { + .toc-nav code { + background: #222222; + } +} + +@media (max-width: 64em) { + .toc-nav { + display: none; + } +} + +.toc-nav ul { + list-style: none; + padding: 0; + margin: 0; +} + +.toc-nav li { + margin-bottom: 0.45rem; +} + +.toc-nav a { + color: inherit; + text-decoration: none; + opacity: 0.6; + transition: opacity 0.15s; +} + +.toc-nav a:hover, +.toc-nav a.active { + opacity: 1; +} diff --git a/site/src/themes.mjs b/site/src/themes.mjs new file mode 100644 index 0000000..913b2ce --- /dev/null +++ b/site/src/themes.mjs @@ -0,0 +1,59 @@ +export const midnight = { + name: "midnight", + type: "dark", + colors: { + "editor.background": "#222222", + "editor.foreground": "#e0e0e0", + }, + tokenColors: [ + { + scope: [ + "storage.type", + "storage.modifier", + "keyword.control", + "keyword.operator.new", + ], + settings: { foreground: "#7aa2f7" }, + }, + { + scope: [ + "string.quoted", + "constant.numeric", + "constant.language", + "constant.character", + "number", + ], + settings: { foreground: "#98c379" }, + }, + ], +}; + +export const daylight = { + name: "daylight", + type: "light", + colors: { + "editor.background": "#ebebeb", + "editor.foreground": "#1a1a1a", + }, + tokenColors: [ + { + scope: [ + "storage.type", + "storage.modifier", + "keyword.control", + "keyword.operator.new", + ], + settings: { foreground: "#3b5bdb" }, + }, + { + scope: [ + "string.quoted", + "constant.numeric", + "constant.language", + "constant.character", + "number", + ], + settings: { foreground: "#2d7f3e" }, + }, + ], +}; diff --git a/site/tsconfig.json b/site/tsconfig.json new file mode 100644 index 0000000..7648092 --- /dev/null +++ b/site/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@layouts/*": ["src/layouts/*"], + "@components/*": ["src/components/*"] + } + } +} From 26ec4528787e41c5487354fcee6a1f7224657a8c Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 15:55:08 -0400 Subject: [PATCH 04/64] public --- site/public/favicon.png | Bin 4914 -> 238 bytes site/public/favicon.svg | 15 +-------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/site/public/favicon.png b/site/public/favicon.png index a2c7383d3056ce9c395739620ae4eca154ab4132..9f1ee56d941d72834f64c853ae50018c1e5ea3c0 100644 GIT binary patch literal 238 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4kiW$hCdQ-7c($0uqAoByDnBz7-E!75Srj0tWtw@9*Y%HjEf3?4AId>q zY2RkJg$iy|47mF93$I`%$F}^>zHf^1K6lpsVM<$|Ew_iOcg6ew_Qk>axXnC#$|D`+oK`w$S%^3=W~v pS}rl4qQxdkJl@{D^M~p@lT~w!HwfN+!@$76;OXk;vd$@?2>`~CTx$RT literal 4914 zcmeAS@N?(olHy`uVBq!ia0y~yV0Z|^9Lx+144k=29t;eO+yOozuK)l4KXc{`;|ya4 z28M=)28LJ`*MCgs&!6A3XAfho_=yuIZr!?-_HRaOYina;Bjaq7xpU{PU%&qQ_wNiT zO6BF{jO7{+A3p5r>N;@Xz}Br>OG-*4BqSIcC9?j{TfTfbLo|btk&(T<{nV*b877*V zJP?2L=1oLIL}FrMaBwi=BwYq)vFz+@h5%`AZ*O^dc|AQn22FN`d{v7VLJUTn0s;aO z9UM_VE0h}<8Diy)_c8_?FOPWFsXWJ)A&iB=i`o1PBV&_}+qw(}B}N7ZW`-gO1`i1a zSw;pOM!h0elO{itgNzI&f`(fd8DuyaMA#X`*rnIGTi;;NTgIS0ok1p@fzOn|XICME zA~!?0fam%X5)2H2k33x*Ln`Lny_;Em`NM`Xn2}@QS2f%2aob}Pj#s`t zjbGJ{9he;@oczH5{=cYXC1$(d-vVnp8Sec1c5!k2wJ9eWeIhRFa4B%eJBS@#w_V_U zg-)fJ1(VAXyq;aR~4T~^~*8N+eEL|!Ktv{9AI;quv<2#Rt%e*6rtEal>{AM+K z@Jftj^_&0my%KH|_FLYWqL(V98sILYa^Tzz+lHcb=S79jJ^pX@Jn^#Sp{IVyMp2xJ zTvr4fExP|3%-{L{$L!NTddv7ewSEj2_{DJS$WIF?4n?-K&Y&AJe_G@h?=kKYYdzE_ zo*)yro8O6bhN9QQ8|ORIyT2v$UN~Jj>ug>$M}6i>#}4b@^M^7n9}>^v3Vl>4+d3t} zJFzXoexcWzg$AnswyrzW@*-TK@AhmVv+JCy7grm|xM#N-+lO|4NLU*nAe?ypaO88t zg1G@J<)rplcQ$A2ogln)!^Sta=0>n?`^{>azhc&TcW=qXsV|LN9}9MA{?__EJ-fw$ zMX}^e>9WilSNc>7CSK-ixh1^%&G!fQH+Jy&iS$jH{!zk#tGl&_!|7V)RSxk_Tf5_a zt~dM3BFpmRy+<-9dzn;kAd`&gss9>>CyGhb#B20)#3@ZD#-jyWpPtu3{RK zB!m|;_Ge@krA|H6_{QMjf$K*P%=qzj{RjT_YIn`*A1ZWtPue(vsrZ+cMd8i|h394+ zK6dEfPgZ`unw@+nI`%|uwh~=`=YHl4?!%I7aW5Ntk2y9hPFJ-#nE$cRc7ovk({rwe zLo*-7EwI)<|?B0HjOOb)Z>@9H~r zyI*JCG!~|hXY2nvI4CFHc)C1~*+7G>t3cFF(rF5-krK~t)dR|tqgJte`nbACcKy3U zpDYCT3qDwp^x#?R;pNv3JYzU#ZNQWl^MdKkXP!7IMswW~hBg_C2M6YEI>5`_zCPAC zgXhkg3CzxguQ~(SmhaK3Y0Y2C@At#CVYbLF2L6iXf>gdc3ob|-=zOYiT*#&K-n-#9 zL(VKlbG0W9KVQl;+*LX&F2Xne2D7__g)wWMhesoCb;3oH_4=A$COF!qr|0qA=?FOa z;1bJ@BL_CJ76?1>`K%N8c3>%Q0rR1Uy~YC9%vqGR6TUi$KagPNf78Hss7`Ys`-6?b z7hEf1oL=x+Y-J78DPin-xK!9ZVXG6L&niXl=81t^Ik%a$Mfu``5-Q_)Vk(%zG(#HI zB;PD?-s8BTfH6$t3!64gW6`{BOfL)WVKbuw_K3^a6>R+YQc|rvOc%0;sqim zUSK`Ju6{&^<*mYBW&t~u1XU-|45RN}Jo<$XRiO=WPA1gHn!#cGzMVUI?j{BCvwOsA;dl}m~FIslXwfZ?o)J{yk zk<&JHyUs7+U#v=tlEwCNiat2QcH;Pkr#>h6|=l zhdxZ2)7tPn=(mDj*hiHV$Imn5XGlo zm{2xvRkTdfz8=dnKie8PO!ZRJP!&);bkk(lN4bLwGl zSBGk)ln*zzZ5C)a`C*OdheZMz-cKa&l!>ujWq#YC)Lhpx?SLhlSikcu;Z(N09Ks3b z*(#!#GW>juUO2yCuGn1W)-Gvy_QAH#Td#=SFl1JGRDVrau;BN5f7#ZPBAkn-PI~4r zSw|@0bYKSOoQIiWs@J|srcPuy-h8(eru4?BJPW!%Kd$L*J-t4tAqh`BC zx!i5}JAK)c*JGg-3G6Zo z2`i5tW;>F@7TYp0;(B4qw!Z745iVC!E@-xg9oT1VFn`7A1WA-u+wZI}{wE6HScSK3x>I<9--?`k}i>OuKE}HsbL3Y_h!2_qe8h`2BdU`>p`Scr| zmaK-TMN&O`WdE-}242d&B7X^!SB843l-jlzZ-fl3+Z( zZojkif-^~?hbFvz)ICdH@waT%=>-QT1)WGzbu!!5o%Q_9uH^d%&o)GdIZSl@e`tcQ zwVk_WO8>@9Hyn((Yf|@U%t+nXB(v!j&#X$JO}^dyZ-X-;T_)??s}|oh?UJ6}lI4Eo z&T|~QT@Tjq99+HO{5owxF{``UAM!6`T`hhReItMK?1u}cGx^896YZ1q+4RC!;@fTJ zif=bUuH4vlHhH?1G}|pbuDo9&hhB6`JUp6s``|Oh<}ht9u7-(W8MnWEo#?p!kX9pK zwDu3-PZz~4xgVAaZDaOLkACD<@qC-X{n-HwPjy;M-qdJbSlLk}BDCPN4vXYGqZhs> z+~fZ+77CW$kvZHSz@z{1*WtC*OtwsaIg%A#|LApL^E|u!+ZOWDCUbEem{SeBm3@l|M@B(->qfJnRL7%d|z;p*bV{VS)~V8FR2MueXGb= z7~XTZX3Fw_JV@_+$4kU;4R*!rvwBuM(>t z+<6j|r9I{K`Ad5R7TmiU$d!4^m3iy5EfZeJRrKHA_d=s5bt+pF*HPwsN5UBY^cBe8 z7w+bM8OrtMjB(?(b+7e~dp(fNsSbDheJP5w)FI4c`OmpOw@herG`l2te8YU(SzL)a zrypnvggI^e$CLk!p;crlW8Tdb6Yf1Q@>#8;bm{uHD=W30)Nv+9E}b_kU0=jum$=iP zz4Uqhkq3P z|LIo7{O99w*~-Eba(0&fv%VH2zPwa!R>{}WD!@FscSGETe#ZGm+b8rrTrK$V@wIE# zznph3Si6`z$ZEll**TAr^kgy`)+*|zf6$!jv}?Xv=hjtERv5f4`DJN0D~#=i`>W;i zCzyULJpF3Nho7t7Ic+@PcTD;6J z^E6t&7;7z=@1ZEGQgctgs#}{m;P&C({Xf<-PdQY)SdS+-QBHtMGp^ZDeTK%?MuT;) z?(zjI%;poi=+u=pcXLA++v98B78osLn76_q&ZCg)C5v#J$7Jo7K4JxoHPRELUUTX$ zd9HFW;LzE5d(sp{4tvkqD{!Ng<*Z92*XTdsKu-Ql_2 z=(+Z`j|4M&mBJS34>Ai6Ii33Po#RJejctLdboGUX*Q*f&+lZ&}j*uulj z-?sdjeQg(S%R?ji87~)T?wd4klhZF{#%1PGD=sWNzWVD6*IQez#m1d*ix+xuH?Etj zewnSog+0#RGN!Flm&4nixNF2Ub{bvTlRX(Y*{fApk6k4a; zeRpr!fvXJ?Rntp+zI0tt>B+vE+N#tnb7FedgVt?X?7X{E_bzJ{G3|{gP1I(5XZEdd zs%%{MEH`V$xw$nu#}?ag-~16d>);!w=&jAmVnaM`zI~_1>eshozwcy|MXgirTJ_9d zus!ZXW76jK2eZ#;UGZT*E4ou`qyB>__uk#}dhm70!GH70>NYU-P2S@FTmOc=*}aak zwFYyT1MGs8RUgSbJcn*xp4NQpWSL){ zbAY5ttw#CA0H(yv?Xe+KosR6ye<91w_42s5eqMm0?8V!6-^Ny*?_Te9eai&7#qv^X zwGXCdTr989D^Hsv$-Le(mQ#>!sOoxt~=ZOK(hz3`9L>|(+D8aeicZ`SN;He<@l@>1$%e7oSG^X$D-Upz}_{W8nv z+sn6WE=bs9TYfolF>}^Kfptz#j@ovs*(slTpL;9J$#3(8FjmFxH51NV-Rbwi@`vuk zyD5uU`Mz2Emc^tr~WCb`v+R^un7B=`k+7aV7yyXPi#FOzb=Vttt1! zzEek*?of4_8zTIPw{&qw=H8N}^&2D8SE?9!?v`J-?7EVV`(+-8ww^ z(e~}tAtzNf*Vy$obtU}U5^1=Bt@hzekNrkx9WrnIv)y$~W^27ox!=Ty;LE8;>?`-| zt!z@7X>Mg>bLY|RzP**l3*6mH?9a)X7Pv<}Q?SjExVf;n!_<^x$D*za=Y#yKu14J7 z+s|%x%RQ}$efHyx+twD%$V%Y!mAZ`<-RD&=9FeD+L7 zu}MGn%#%z}c2W6g$cJp=L R7#J8BJYD@<);T3K0RW<2{Fnd$ diff --git a/site/public/favicon.svg b/site/public/favicon.svg index 884624f..1f51ad8 100644 --- a/site/public/favicon.svg +++ b/site/public/favicon.svg @@ -1,14 +1 @@ - - - :h - + \ No newline at end of file From 600fd6503bfe25a72d0fe58b97e46591e2b191ed Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 15:55:53 -0400 Subject: [PATCH 05/64] base --- site/src/pages/index.astro | 2 +- site/src/styles/base.css | 56 +------------------------------------- 2 files changed, 2 insertions(+), 56 deletions(-) diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 4858a1d..607835e 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -5,7 +5,7 @@ import DocLayout from "../layouts/DocLayout.astro";

deskctl

- desktop control CLI for AI agents +

diff --git a/site/src/styles/base.css b/site/src/styles/base.css index f1bd0f4..84abc45 100644 --- a/site/src/styles/base.css +++ b/site/src/styles/base.css @@ -1,57 +1,3 @@ -@font-face { - font-family: "Signifier"; - src: url("https://www.barrettruth.com/fonts/signifier/Signifier-Regular.ttf") - format("truetype"); - font-weight: 400; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: "Signifier"; - src: url("https://www.barrettruth.com/fonts/signifier/Signifier-RegularItalic.ttf") - format("truetype"); - font-weight: 400; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: "Signifier"; - src: url("https://www.barrettruth.com/fonts/signifier/Signifier-Light.ttf") - format("truetype"); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: "Signifier"; - src: url("https://www.barrettruth.com/fonts/signifier/Signifier-LightItalic.ttf") - format("truetype"); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: "Signifier"; - src: url("https://www.barrettruth.com/fonts/signifier/Signifier-Medium.ttf") - format("truetype"); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: "Signifier"; - src: url("https://www.barrettruth.com/fonts/signifier/Signifier-Bold.ttf") - format("truetype"); - font-weight: 700; - font-style: normal; - font-display: swap; -} - @font-face { font-family: "Berkeley Mono"; src: url("https://www.barrettruth.com/fonts/berkeley-mono/BerkeleyMono-Regular.ttf") @@ -89,7 +35,7 @@ html, body { - font-family: "Signifier", serif; + font-family: "Berkeley Mono", monospace; line-height: 1.65; color: #1a1a1a; background: #f5f5f5; From e3c96878b19d27b1aefeb1e4d6f4f954318b370c Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 16:01:43 -0400 Subject: [PATCH 06/64] scaffold docs --- site/src/pages/architecture.mdx | 67 +++++++++++- site/src/pages/commands.mdx | 176 ++++++++++++++++++++++++++++++++ site/src/pages/index.astro | 16 ++- site/src/pages/installation.mdx | 35 ++++--- site/src/pages/quick-start.mdx | 87 ++++++++++++++++ site/src/pages/usage.mdx | 53 ---------- 6 files changed, 356 insertions(+), 78 deletions(-) create mode 100644 site/src/pages/commands.mdx create mode 100644 site/src/pages/quick-start.mdx delete mode 100644 site/src/pages/usage.mdx diff --git a/site/src/pages/architecture.mdx b/site/src/pages/architecture.mdx index 064f995..ec874e2 100644 --- a/site/src/pages/architecture.mdx +++ b/site/src/pages/architecture.mdx @@ -8,12 +8,71 @@ toc: true ## Client-daemon model -deskctl uses a client-daemon architecture over Unix sockets with an NDJSON wire protocol. The daemon starts automatically on the first command and keeps the X11 connection alive for fast repeated calls. +deskctl uses a client-daemon architecture over Unix sockets. The daemon starts automatically on the first command and keeps the X11 connection alive so repeated calls skip the connection setup overhead. + +Each command opens a new connection to the daemon, sends a single NDJSON request, reads one NDJSON response, and exits. + +## Wire protocol + +Requests and responses are newline-delimited JSON (NDJSON) over a Unix socket. + +**Request:** + +```json +{"id": "r123456", "action": "snapshot", "annotate": true} +``` + +**Response:** + +```json +{"success": true, "data": {"screenshot": "/tmp/deskctl-1234567890.png", "windows": [...]}} +``` + +Error responses include an `error` field: + +```json +{"success": false, "error": "window not found: @w99"} +``` + +## Socket location + +The daemon socket is resolved in this order: + +1. `--socket` flag (highest priority) +2. `$DESKCTL_SOCKET_DIR/{session}.sock` +3. `$XDG_RUNTIME_DIR/deskctl/{session}.sock` +4. `~/.deskctl/{session}.sock` + +PID files are stored alongside the socket. + +## Sessions + +Multiple isolated daemon instances can run simultaneously using the `--session` flag: + +```sh +deskctl --session workspace1 snapshot +deskctl --session workspace2 snapshot +``` + +Each session has its own socket, PID file, and window ref map. ## Backend design -The backend is trait-based, making it straightforward to add support for different display servers. The current implementation targets X11 via `x11rb`. +The core is built around a `DesktopBackend` trait. The current implementation uses `x11rb` for X11 protocol operations and `enigo` for input simulation. -## Wayland support +The trait-based design means adding Wayland support is a single trait implementation with no changes to the core, CLI, or daemon code. -Coming soon. The trait-based backend design means adding Hyprland/Wayland support is a single trait implementation with zero refactoring of the core. +## X11 integration + +Window detection uses EWMH properties: + +| Property | Purpose | +|----------|---------| +| `_NET_CLIENT_LIST_STACKING` | Window stacking order | +| `_NET_ACTIVE_WINDOW` | Currently focused window | +| `_NET_WM_NAME` | Window title (UTF-8) | +| `_NET_WM_STATE_HIDDEN` | Minimized state | +| `_NET_CLOSE_WINDOW` | Graceful close | +| `WM_CLASS` | Application class/name | + +Falls back to `XQueryTree` if `_NET_CLIENT_LIST_STACKING` is unavailable. diff --git a/site/src/pages/commands.mdx b/site/src/pages/commands.mdx new file mode 100644 index 0000000..bd639c7 --- /dev/null +++ b/site/src/pages/commands.mdx @@ -0,0 +1,176 @@ +--- +layout: ../layouts/DocLayout.astro +title: Commands +toc: true +--- + +# Commands + +## Snapshot + +Capture a screenshot and get the window tree: + +```sh +deskctl snapshot +deskctl snapshot --annotate +``` + +With `--annotate`, colored bounding boxes and `@wN` labels are drawn on the screenshot. Each window gets a unique color from an 8-color palette. Minimized windows are skipped. + +The screenshot is saved to `/tmp/deskctl-{timestamp}.png`. + +## Click + +Click the center of a window by ref, or click exact coordinates: + +```sh +deskctl click @w1 +deskctl click 960,540 +``` + +## Double click + +```sh +deskctl dblclick @w1 +deskctl dblclick 500,300 +``` + +## Type + +Type a string into the focused window: + +```sh +deskctl type "hello world" +``` + +## Press + +Press a single key: + +```sh +deskctl press enter +deskctl press tab +deskctl press escape +``` + +Supported key names: `enter`, `tab`, `escape`, `backspace`, `delete`, `space`, `up`, `down`, `left`, `right`, `home`, `end`, `pageup`, `pagedown`, `f1`-`f12`, or any single character. + +## Hotkey + +Send a key combination. List modifier keys first, then the target key: + +```sh +deskctl hotkey ctrl c +deskctl hotkey ctrl shift t +deskctl hotkey alt f4 +``` + +Modifier names: `ctrl`, `alt`, `shift`, `super` (also `meta` or `win`). + +## Mouse move + +Move the cursor to absolute coordinates: + +```sh +deskctl mouse move 100 200 +``` + +## Mouse scroll + +Scroll the mouse wheel. Positive values scroll down, negative scroll up: + +```sh +deskctl mouse scroll 3 +deskctl mouse scroll -5 +deskctl mouse scroll 3 --axis horizontal +``` + +## Mouse drag + +Drag from one position to another: + +```sh +deskctl mouse drag 100 200 500 600 +``` + +## Focus + +Focus a window by ref or by name (case-insensitive substring match): + +```sh +deskctl focus @w1 +deskctl focus "firefox" +``` + +## Close + +Close a window gracefully: + +```sh +deskctl close @w2 +deskctl close "terminal" +``` + +## Move window + +Move a window to an absolute position: + +```sh +deskctl move-window @w1 0 0 +deskctl move-window "firefox" 100 100 +``` + +## Resize window + +Resize a window: + +```sh +deskctl resize-window @w1 1280 720 +``` + +## List windows + +List all windows without taking a screenshot: + +```sh +deskctl list-windows +``` + +## Get screen size + +```sh +deskctl get-screen-size +``` + +## Get mouse position + +```sh +deskctl get-mouse-position +``` + +## Screenshot + +Take a screenshot without the window tree. Optionally specify a save path: + +```sh +deskctl screenshot +deskctl screenshot /tmp/my-screenshot.png +deskctl screenshot --annotate +``` + +## Launch + +Launch an application: + +```sh +deskctl launch firefox +deskctl launch code --args /path/to/project +``` + +## Global options + +| Flag | Env | Description | +|------|-----|-------------| +| `--json` | | Output as JSON | +| `--socket ` | `DESKCTL_SOCKET` | Path to daemon Unix socket | +| `--session ` | | Session name for multiple daemons (default: `default`) | diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 607835e..8fcd07c 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -9,16 +9,22 @@ import DocLayout from "../layouts/DocLayout.astro";

- X11 desktop control CLI for AI agents on Linux. Snapshot, click, type, and - focus windows through a simple command-line interface with a client-daemon - architecture over Unix sockets. + Desktop control CLI for AI agents on Linux X11. Compact JSON output + for agent loops. Screenshot, click, type, scroll, drag, and manage + windows through a fast client-daemon architecture. 100% native Rust.

-

Documentation

+

Getting started

+ +

Reference

+ + diff --git a/site/src/pages/installation.mdx b/site/src/pages/installation.mdx index faeca27..e05772d 100644 --- a/site/src/pages/installation.mdx +++ b/site/src/pages/installation.mdx @@ -1,6 +1,7 @@ --- layout: ../layouts/DocLayout.astro title: Installation +toc: true --- # Installation @@ -11,9 +12,17 @@ title: Installation cargo install deskctl ``` -## Docker build +## From source -Build a Linux binary with Docker: +```sh +git clone https://github.com/harivansh-afk/deskctl +cd deskctl +cargo build --release +``` + +## Docker (cross-compile for Linux) + +Build a static Linux binary from any platform: ```sh docker compose -f docker/docker-compose.yml run --rm build @@ -21,25 +30,19 @@ docker compose -f docker/docker-compose.yml run --rm build This writes `dist/deskctl-linux-x86_64`. -## From source - -```sh -git clone https://github.com/harivansh-afk/deskctl -cd deskctl -cargo build -``` - ## Deploy to a remote machine -Copy the binary to an SSH machine: +Copy the binary over SSH when `scp` is not available: ```sh -ssh -p 443 deskctl@ssh.agentcomputer.ai 'cat > ~/deskctl && chmod +x ~/deskctl' < dist/deskctl-linux-x86_64 +ssh -p 443 user@host 'cat > ~/deskctl && chmod +x ~/deskctl' < dist/deskctl-linux-x86_64 ``` -## Runtime requirements +## Requirements -- Linux with X11 session -- `DISPLAY` environment variable set +- Linux with an active X11 session +- `DISPLAY` environment variable set (e.g. `DISPLAY=:1`) - `XDG_SESSION_TYPE=x11` -- A window manager exposing standard EWMH properties +- A window manager that exposes EWMH properties (`_NET_CLIENT_LIST_STACKING`, `_NET_ACTIVE_WINDOW`) + +No extra native libraries are needed beyond the standard glibc runtime (`libc`, `libm`, `libgcc_s`). diff --git a/site/src/pages/quick-start.mdx b/site/src/pages/quick-start.mdx new file mode 100644 index 0000000..7f3bc07 --- /dev/null +++ b/site/src/pages/quick-start.mdx @@ -0,0 +1,87 @@ +--- +layout: ../layouts/DocLayout.astro +title: Quick start +toc: true +--- + +# Quick start + +## Core workflow + +The typical agent loop is: snapshot the desktop, interpret the result, act on it. + +```sh +# 1. see the desktop +deskctl --json snapshot --annotate + +# 2. click a window by its ref +deskctl click @w1 + +# 3. type into the focused window +deskctl type "hello world" + +# 4. press a key +deskctl press enter +``` + +The `--annotate` flag draws colored bounding boxes and `@wN` labels on the screenshot so agents can visually identify windows. + +## Window refs + +Every `snapshot` assigns refs like `@w1`, `@w2`, etc. to each visible window, ordered top-to-bottom by stacking order. Use these refs anywhere a selector is expected: + +```sh +deskctl click @w1 +deskctl focus @w3 +deskctl close @w2 +``` + +You can also select windows by name (case-insensitive substring match): + +```sh +deskctl focus "firefox" +deskctl close "terminal" +``` + +## JSON output + +Pass `--json` for machine-readable output. This is the primary mode for agent integrations: + +```sh +deskctl --json snapshot +``` + +```json +{ + "success": true, + "data": { + "screenshot": "/tmp/deskctl-1234567890.png", + "windows": [ + { + "ref_id": "w1", + "xcb_id": 12345678, + "title": "Firefox", + "app_name": "firefox", + "x": 0, + "y": 0, + "width": 1920, + "height": 1080, + "focused": true, + "minimized": false + } + ] + } +} +``` + +## Daemon lifecycle + +The daemon starts automatically on the first command. It keeps the X11 connection alive so repeated calls are fast. You do not need to manage it manually. + +```sh +# check if the daemon is running +deskctl daemon status + +# stop it explicitly +deskctl daemon stop +``` diff --git a/site/src/pages/usage.mdx b/site/src/pages/usage.mdx deleted file mode 100644 index 43118f6..0000000 --- a/site/src/pages/usage.mdx +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: ../layouts/DocLayout.astro -title: Usage -toc: true ---- - -# Usage - -## Snapshot - -Capture the current desktop state: - -```sh -deskctl snapshot -``` - -With annotations overlaid on windows: - -```sh -deskctl --json snapshot --annotate -``` - -## Click - -Click a window by its annotation handle: - -```sh -deskctl click @w1 -``` - -## Type - -Type text into the focused window: - -```sh -deskctl type "hello world" -``` - -## Focus - -Focus a window by name: - -```sh -deskctl focus "firefox" -``` - -## JSON output - -Pass `--json` for machine-readable output, useful for agent integrations: - -```sh -deskctl --json snapshot -``` From 9a178e0b3ad02f3f2a2868796c7910b2e0193944 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 16:02:49 -0400 Subject: [PATCH 07/64] font --- site/src/styles/base.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/src/styles/base.css b/site/src/styles/base.css index 84abc45..86fd6a8 100644 --- a/site/src/styles/base.css +++ b/site/src/styles/base.css @@ -1,3 +1,5 @@ +@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;700&display=swap"); + @font-face { font-family: "Berkeley Mono"; src: url("https://www.barrettruth.com/fonts/berkeley-mono/BerkeleyMono-Regular.ttf") @@ -35,7 +37,7 @@ html, body { - font-family: "Berkeley Mono", monospace; + font-family: "Manrope", sans-serif; line-height: 1.65; color: #1a1a1a; background: #f5f5f5; From 07124001eb8aafb45eda780434ad630d8202930c Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 16:24:27 -0400 Subject: [PATCH 08/64] favicon --- site/public/favicon.png | Bin 238 -> 648 bytes site/public/favicon.svg | 7 ++++++- site/src/pages/index.astro | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/site/public/favicon.png b/site/public/favicon.png index 9f1ee56d941d72834f64c853ae50018c1e5ea3c0..1c3771a322460c41bc614990ca5320b142d69e37 100644 GIT binary patch delta 618 zcmaFI*ugqMl7o$bfuXpn>dQn$k$NUuPZ!6Kh}NUiH)bn2inQ;SOcnIgJ<{8@#6!V% zn`dF>6qTl})32DBDK4F``qHu;kF-{2H~nu&C@$d8N;E9ETe(#1`clu$0v9R-j(m+R zRCM(_`7Gvcy!q#{AGOAND)YlWs+`{BHzi2kHATMSc+r<=m%5C#+I!>POFM8Rl$Y0k z-yHOcdjqS4w-LL+l%QybizdE@3uU4srwfJ1Jk{If+SZOe z{D=N{{+8-s;}E#aIEz_ep7@Eh%}+j89(uTQe#NB}qlGtfF1;;FKQ=Wr@>7wKTz_$K z@!=AyR2w;cmIMhlr9%e~GWPfP|38wRr$5stsa}F@`t<2nUVm+6@LDP)E-tPj#QE;s zyFHA5`pe47PCP9-|M>Rp=ymJY$HzXGKJf9Q;qv9n&p$qya_HW@d#hj9>}H+n)#{_> ztTuUJ*y>8Zxchz#2^KO;do&BD2eGp?Gd3SouxQBI>a{T71HZytzx^u}cCI`9_wQbN zhKB_++!i~otPlCb^FgeVsl!FdW%22kk@Jcb!pnw+nT|7GHiT(CJcR zDB0`wq{#C9+aC`fHg4W*+}GE4p=x8)(_MPoa?hKzTdHsHV*RHUe&v9M!~cb16K}3R zdUeI>t=Fb5xZ}D+s7U7d^vqdo1v|WomtI+3x4&MLsX*qq-_js^*A!WU$b0giIDY6U gJ+0ec&-#Hq`^TPiL7`Sp1_lNOPgg&ebxsLQ0KT;-y#N3J delta 205 zcmeBReaARKl7oqXf#HwD+r<+VMd}$Ad%8G=cyzv \ No newline at end of file + + + + + + diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 8fcd07c..a33ab8c 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -3,9 +3,9 @@ import DocLayout from "../layouts/DocLayout.astro"; --- -
+

deskctl

- +

From f241dcc1c27f2c69f208676c6efc881e667fbc73 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 16:25:14 -0400 Subject: [PATCH 09/64] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f65d7b2..99ec28c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ target/ .humanlayer/ tmp/ +.vercel +secret/ From 6845f6b7e4a96d9dc6869615bb6df3a25c69b9cc Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 16:31:15 -0400 Subject: [PATCH 10/64] Acknowledgements --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fb9dcd0..9167596 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,7 @@ For deskctl to be fully functional on a fresh VM you still need: ## Wayland Support Coming soon. The trait-based backend design means adding Hyprland/Wayland support is a single trait implementation with zero refactoring of the core which is good. + +## Acknowledgements + +- [@barrettruth](github.com/barrettruth) - i stole the website from [vimdoc](https://github.com/barrettruth/vimdoc-language-server) From 4bd14c0da301822def8279d2df0f880733e31f69 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 16:44:58 -0400 Subject: [PATCH 11/64] ci --- .github/workflows/ci.yml | 144 ++++++++++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5db7ea5..84950e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,70 @@ permissions: packages: write jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + rust: ${{ steps.check.outputs.rust }} + steps: + - uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + rust: + - 'src/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'docker/**' + + - name: Set outputs + id: check + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "rust=true" >> "$GITHUB_OUTPUT" + else + echo "rust=${{ steps.filter.outputs.rust }}" >> "$GITHUB_OUTPUT" + fi + + version: + name: Next Version + needs: changes + if: needs.changes.outputs.rust == 'true' + runs-on: ubuntu-latest + outputs: + version: ${{ steps.next.outputs.version }} + tag: ${{ steps.next.outputs.tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Calculate next version + id: next + run: | + BASE=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE" + + LATEST=$(git tag -l "v${MAJOR}.${MINOR}.*" | sort -V | tail -1) + + if [ -z "$LATEST" ]; then + NEW="$BASE" + else + LATEST_VER="${LATEST#v}" + IFS='.' read -r _ _ LATEST_PATCH <<< "$LATEST_VER" + NEW_PATCH=$((LATEST_PATCH + 1)) + NEW="${MAJOR}.${MINOR}.${NEW_PATCH}" + fi + + echo "version=${NEW}" >> "$GITHUB_OUTPUT" + echo "tag=v${NEW}" >> "$GITHUB_OUTPUT" + cargo: - name: Cargo Build + name: Build and Test + needs: [changes, version] + if: needs.changes.outputs.rust == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -42,15 +104,13 @@ jobs: retention-days: 7 docker: - name: Docker Build + name: Docker + needs: [changes, version, cargo] + if: needs.changes.outputs.rust == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Get version - id: version - run: echo "version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')" >> "$GITHUB_OUTPUT" - - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 @@ -66,47 +126,65 @@ jobs: push: true tags: | ghcr.io/${{ github.repository }}:latest - ghcr.io/${{ github.repository }}:v${{ steps.version.outputs.version }} + ghcr.io/${{ github.repository }}:${{ needs.version.outputs.tag }} cache-from: type=gha cache-to: type=gha,mode=max - release: - name: Release - needs: [cargo, docker] + update-manifests: + name: Update Manifests + needs: [version, cargo, docker] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Get version - id: version - run: echo "version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')" >> "$GITHUB_OUTPUT" + - uses: dtolnay/rust-toolchain@stable + + - name: Update version in Cargo.toml + run: | + CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + NEW="${{ needs.version.outputs.version }}" + if [ "$CURRENT" != "$NEW" ]; then + sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml + cargo generate-lockfile + fi + + - name: Commit, tag, and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + if ! git diff --quiet; then + git add Cargo.toml Cargo.lock + git commit -m "release: ${{ needs.version.outputs.tag }} [skip ci]" + fi + + git tag "${{ needs.version.outputs.tag }}" + git push origin main --tags + + release: + name: Release + needs: [version, cargo, update-manifests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: deskctl-linux-x86_64 path: artifacts/ - - name: Prepare release assets - run: | - chmod +x artifacts/deskctl - mv artifacts/deskctl artifacts/deskctl-linux-x86_64 - - - name: Create or update release + - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - TAG="v${{ steps.version.outputs.version }}" - IMAGE="ghcr.io/${{ github.repository }}:${TAG}" + chmod +x artifacts/deskctl + mv artifacts/deskctl artifacts/deskctl-linux-x86_64 + cd artifacts && sha256sum deskctl-linux-x86_64 > checksums.txt && cd .. - BODY="## Artifacts - - **Binary:** \`deskctl-linux-x86_64\` (attached) - - **Docker:** \`docker pull ${IMAGE}\`" - - if gh release view "$TAG" &>/dev/null; then - gh release upload "$TAG" artifacts/deskctl-linux-x86_64 --clobber - else - gh release create "$TAG" \ - --title "$TAG" \ - --notes "${BODY}" \ - artifacts/deskctl-linux-x86_64 - fi + gh release create "${{ needs.version.outputs.tag }}" \ + --title "${{ needs.version.outputs.tag }}" \ + --generate-notes \ + artifacts/deskctl-linux-x86_64 \ + artifacts/checksums.txt From a6ea5cc9e8471ada869f5d09ca53e0593da2b907 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 16:54:11 -0400 Subject: [PATCH 12/64] changes (detect paths + calculate version) | build [cargo, docker] (parallel matrix) | update-manifests (bump Cargo.toml, commit, tag) | release (GitHub release + checksums) --- .github/workflows/ci.yml | 67 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84950e3..469d98c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,12 +15,16 @@ permissions: jobs: changes: - name: Detect Changes + name: Changes runs-on: ubuntu-latest outputs: rust: ${{ steps.check.outputs.rust }} + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: dorny/paths-filter@v3 id: filter @@ -41,21 +45,9 @@ jobs: echo "rust=${{ steps.filter.outputs.rust }}" >> "$GITHUB_OUTPUT" fi - version: - name: Next Version - needs: changes - if: needs.changes.outputs.rust == 'true' - runs-on: ubuntu-latest - outputs: - version: ${{ steps.next.outputs.version }} - tag: ${{ steps.next.outputs.tag }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Calculate next version - id: next + id: version + if: steps.check.outputs.rust == 'true' run: | BASE=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE" @@ -74,65 +66,72 @@ jobs: echo "version=${NEW}" >> "$GITHUB_OUTPUT" echo "tag=v${NEW}" >> "$GITHUB_OUTPUT" - cargo: - name: Build and Test - needs: [changes, version] + build: + name: Build (${{ matrix.target }}) + needs: changes if: needs.changes.outputs.rust == 'true' runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + target: [cargo, docker] steps: - uses: actions/checkout@v4 + # --- Cargo steps --- - uses: dtolnay/rust-toolchain@stable + if: matrix.target == 'cargo' with: components: clippy - uses: Swatinem/rust-cache@v2 + if: matrix.target == 'cargo' - name: Install system dependencies + if: matrix.target == 'cargo' run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev - name: Clippy + if: matrix.target == 'cargo' run: cargo clippy -- -D warnings - name: Build + if: matrix.target == 'cargo' run: cargo build --release --locked - uses: actions/upload-artifact@v4 + if: matrix.target == 'cargo' with: name: deskctl-linux-x86_64 path: target/release/deskctl retention-days: 7 - docker: - name: Docker - needs: [changes, version, cargo] - if: needs.changes.outputs.rust == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - + # --- Docker steps --- - uses: docker/setup-buildx-action@v3 + if: matrix.target == 'docker' - uses: docker/login-action@v3 + if: matrix.target == 'docker' with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 + if: matrix.target == 'docker' with: context: . file: docker/Dockerfile push: true tags: | ghcr.io/${{ github.repository }}:latest - ghcr.io/${{ github.repository }}:${{ needs.version.outputs.tag }} + ghcr.io/${{ github.repository }}:${{ needs.changes.outputs.tag }} cache-from: type=gha cache-to: type=gha,mode=max update-manifests: name: Update Manifests - needs: [version, cargo, docker] + needs: [changes, build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -144,7 +143,7 @@ jobs: - name: Update version in Cargo.toml run: | CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - NEW="${{ needs.version.outputs.version }}" + NEW="${{ needs.changes.outputs.version }}" if [ "$CURRENT" != "$NEW" ]; then sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml cargo generate-lockfile @@ -157,15 +156,15 @@ jobs: if ! git diff --quiet; then git add Cargo.toml Cargo.lock - git commit -m "release: ${{ needs.version.outputs.tag }} [skip ci]" + git commit -m "release: ${{ needs.changes.outputs.tag }} [skip ci]" fi - git tag "${{ needs.version.outputs.tag }}" + git tag "${{ needs.changes.outputs.tag }}" git push origin main --tags release: name: Release - needs: [version, cargo, update-manifests] + needs: [changes, build, update-manifests] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -183,8 +182,8 @@ jobs: mv artifacts/deskctl artifacts/deskctl-linux-x86_64 cd artifacts && sha256sum deskctl-linux-x86_64 > checksums.txt && cd .. - gh release create "${{ needs.version.outputs.tag }}" \ - --title "${{ needs.version.outputs.tag }}" \ + gh release create "${{ needs.changes.outputs.tag }}" \ + --title "${{ needs.changes.outputs.tag }}" \ --generate-notes \ artifacts/deskctl-linux-x86_64 \ artifacts/checksums.txt From d487a6020943bd14eb62b9694e6bc9bd324cf7f6 Mon Sep 17 00:00:00 2001 From: Hari <73809867+harivansh-afk@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:31:55 -0400 Subject: [PATCH 13/64] openspec init (#2) --- .claude/commands/opsx/apply.md | 152 +++++++++ .claude/commands/opsx/archive.md | 157 ++++++++++ .claude/commands/opsx/explore.md | 173 +++++++++++ .claude/commands/opsx/propose.md | 106 +++++++ .claude/skills/openspec-apply-change/SKILL.md | 156 ++++++++++ .../skills/openspec-archive-change/SKILL.md | 114 +++++++ .claude/skills/openspec-explore/SKILL.md | 288 ++++++++++++++++++ .claude/skills/openspec-propose/SKILL.md | 110 +++++++ .codex/skills/openspec-apply-change/SKILL.md | 156 ++++++++++ .../skills/openspec-archive-change/SKILL.md | 114 +++++++ .codex/skills/openspec-explore/SKILL.md | 288 ++++++++++++++++++ .codex/skills/openspec-propose/SKILL.md | 110 +++++++ .gitignore | 1 - openspec/config.yaml | 20 ++ 14 files changed, 1944 insertions(+), 1 deletion(-) create mode 100644 .claude/commands/opsx/apply.md create mode 100644 .claude/commands/opsx/archive.md create mode 100644 .claude/commands/opsx/explore.md create mode 100644 .claude/commands/opsx/propose.md create mode 100644 .claude/skills/openspec-apply-change/SKILL.md create mode 100644 .claude/skills/openspec-archive-change/SKILL.md create mode 100644 .claude/skills/openspec-explore/SKILL.md create mode 100644 .claude/skills/openspec-propose/SKILL.md create mode 100644 .codex/skills/openspec-apply-change/SKILL.md create mode 100644 .codex/skills/openspec-archive-change/SKILL.md create mode 100644 .codex/skills/openspec-explore/SKILL.md create mode 100644 .codex/skills/openspec-propose/SKILL.md create mode 100644 openspec/config.yaml diff --git a/.claude/commands/opsx/apply.md b/.claude/commands/opsx/apply.md new file mode 100644 index 0000000..bf23721 --- /dev/null +++ b/.claude/commands/opsx/apply.md @@ -0,0 +1,152 @@ +--- +name: "OPSX: Apply" +description: Implement tasks from an OpenSpec change (Experimental) +category: Workflow +tags: [workflow, artifacts, experimental] +--- + +Implement tasks from an OpenSpec change. + +**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **Select the change** + + If a name is provided, use it. Otherwise: + - Infer from conversation context if the user mentioned a change + - Auto-select if only one active change exists + - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select + + Always announce: "Using change: " and how to override (e.g., `/opsx:apply `). + +2. **Check status to understand the schema** + ```bash + openspec status --change "" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) + +3. **Get apply instructions** + + ```bash + openspec instructions apply --change "" --json + ``` + + This returns: + - Context file paths (varies by schema) + - Progress (total, complete, remaining) + - Task list with status + - Dynamic instruction based on current state + + **Handle states:** + - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue` + - If `state: "all_done"`: congratulate, suggest archive + - Otherwise: proceed to implementation + +4. **Read context files** + + Read the files listed in `contextFiles` from the apply instructions output. + The files depend on the schema being used: + - **spec-driven**: proposal, specs, design, tasks + - Other schemas: follow the contextFiles from CLI output + +5. **Show current progress** + + Display: + - Schema being used + - Progress: "N/M tasks complete" + - Remaining tasks overview + - Dynamic instruction from CLI + +6. **Implement tasks (loop until done or blocked)** + + For each pending task: + - Show which task is being worked on + - Make the code changes required + - Keep changes minimal and focused + - Mark task complete in the tasks file: `- [ ]` → `- [x]` + - Continue to next task + + **Pause if:** + - Task is unclear → ask for clarification + - Implementation reveals a design issue → suggest updating artifacts + - Error or blocker encountered → report and wait for guidance + - User interrupts + +7. **On completion or pause, show status** + + Display: + - Tasks completed this session + - Overall progress: "N/M tasks complete" + - If all done: suggest archive + - If paused: explain why and wait for guidance + +**Output During Implementation** + +``` +## Implementing: (schema: ) + +Working on task 3/7: +[...implementation happening...] +✓ Task complete + +Working on task 4/7: +[...implementation happening...] +✓ Task complete +``` + +**Output On Completion** + +``` +## Implementation Complete + +**Change:** +**Schema:** +**Progress:** 7/7 tasks complete ✓ + +### Completed This Session +- [x] Task 1 +- [x] Task 2 +... + +All tasks complete! You can archive this change with `/opsx:archive`. +``` + +**Output On Pause (Issue Encountered)** + +``` +## Implementation Paused + +**Change:** +**Schema:** +**Progress:** 4/7 tasks complete + +### Issue Encountered + + +**Options:** +1.

- Desktop control CLI for AI agents on Linux X11. Compact JSON output - for agent loops. Screenshot, click, type, scroll, drag, and manage - windows through a fast client-daemon architecture. 100% native Rust. + Desktop control CLI for AI agents on Linux X11. Compact JSON output for + agent loops. Screenshot, click, type, scroll, drag, and manage windows + through a fast client-daemon architecture. 100% native Rust.

Getting started

diff --git a/src/cli/connection.rs b/src/cli/connection.rs index 1237a85..840e637 100644 --- a/src/cli/connection.rs +++ b/src/cli/connection.rs @@ -6,10 +6,10 @@ use std::process::{Command, Stdio}; use std::thread; use std::time::Duration; -use anyhow::{Context, Result, bail}; +use anyhow::{bail, Context, Result}; use crate::cli::GlobalOpts; -use crate::core::doctor::{DoctorReport, run as run_doctor_report}; +use crate::core::doctor::{run as run_doctor_report, DoctorReport}; use crate::core::paths::{pid_path_for_session, socket_dir, socket_path_for_session}; use crate::core::protocol::{Request, Response}; @@ -95,7 +95,8 @@ fn send_request_over_stream(mut stream: UnixStream, request: &Request) -> Result } fn ping_daemon(opts: &GlobalOpts) -> Result<()> { - let response = send_request_over_stream(connect_socket(&socket_path(opts))?, &Request::new("ping"))?; + let response = + send_request_over_stream(connect_socket(&socket_path(opts))?, &Request::new("ping"))?; if response.success { Ok(()) } else { @@ -212,7 +213,9 @@ pub fn daemon_status(opts: &GlobalOpts) -> Result<()> { let path = socket_path(opts); match ping_daemon(opts) { Ok(()) => println!("Daemon running ({})", path.display()), - Err(_) if path.exists() => println!("Daemon socket exists but is unhealthy ({})", path.display()), + Err(_) if path.exists() => { + println!("Daemon socket exists but is unhealthy ({})", path.display()) + } Err(_) => println!("Daemon not running"), } Ok(()) @@ -226,7 +229,11 @@ fn print_doctor_report(report: &DoctorReport, json_output: bool) -> Result<()> { println!( "deskctl doctor: {}", - if report.healthy { "healthy" } else { "issues found" } + if report.healthy { + "healthy" + } else { + "issues found" + } ); for check in &report.checks { let status = if check.ok { "OK" } else { "FAIL" }; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 3500522..d4003ff 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,4 +1,4 @@ -mod connection; +pub mod connection; use anyhow::Result; use clap::{Args, Parser, Subcommand}; diff --git a/src/core/doctor.rs b/src/core/doctor.rs index 3d240c3..e9c4b99 100644 --- a/src/core/doctor.rs +++ b/src/core/doctor.rs @@ -84,13 +84,16 @@ pub fn run(socket_path: &Path) -> DoctorReport { checks.push(match backend.capture_screenshot() { Ok(image) => check_ok( "screenshot", - format!("Captured {}x{} desktop image", image.width(), image.height()), + format!( + "Captured {}x{} desktop image", + image.width(), + image.height() + ), ), Err(error) => check_fail( "screenshot", error.to_string(), - "Verify the X11 session permits desktop capture on the active display." - .to_string(), + "Verify the X11 session permits desktop capture on the active display.".to_string(), ), }); } else { @@ -117,7 +120,10 @@ fn check_socket_dir(socket_path: &Path) -> DoctorCheck { let Some(socket_dir) = socket_path.parent() else { return check_fail( "socket-dir", - format!("Socket path {} has no parent directory", socket_path.display()), + format!( + "Socket path {} has no parent directory", + socket_path.display() + ), "Use a socket path inside a writable directory.".to_string(), ); }; @@ -203,37 +209,3 @@ fn check_fail(name: &str, details: String, fix: String) -> DoctorCheck { fix: Some(fix), } } - -#[cfg(all(test, target_os = "linux"))] -mod tests { - use super::run; - use crate::test_support::{X11TestEnv, env_lock}; - - #[test] - fn doctor_reports_healthy_x11_environment_under_xvfb() { - let _guard = env_lock().lock().unwrap(); - let Some(env) = X11TestEnv::new().unwrap() else { - eprintln!("Skipping Xvfb-dependent doctor test"); - return; - }; - env.create_window("deskctl doctor test", "DeskctlDoctor").unwrap(); - - let socket_path = std::env::temp_dir().join("deskctl-doctor-test.sock"); - let report = run(&socket_path); - - assert!(report.checks.iter().any(|check| check.name == "display" && check.ok)); - assert!(report.checks.iter().any(|check| check.name == "backend" && check.ok)); - assert!( - report - .checks - .iter() - .any(|check| check.name == "window-enumeration" && check.ok) - ); - assert!( - report - .checks - .iter() - .any(|check| check.name == "screenshot" && check.ok) - ); - } -} diff --git a/src/core/refs.rs b/src/core/refs.rs index 101b00c..6185ebf 100644 --- a/src/core/refs.rs +++ b/src/core/refs.rs @@ -136,8 +136,12 @@ impl RefMap { /// Resolve a selector to the center coordinates of the window. pub fn resolve_to_center(&self, selector: &str) -> Option<(i32, i32)> { - self.resolve(selector) - .map(|entry| (entry.x + entry.width as i32 / 2, entry.y + entry.height as i32 / 2)) + self.resolve(selector).map(|entry| { + ( + entry.x + entry.width as i32 / 2, + entry.y + entry.height as i32 / 2, + ) + }) } pub fn entries(&self) -> impl Iterator { @@ -182,7 +186,10 @@ mod tests { assert_eq!(refs.resolve("@w1").unwrap().window_id, window_id); assert_eq!(refs.resolve(&window_id).unwrap().backend_window_id, 42); - assert_eq!(refs.resolve(&format!("id={window_id}")).unwrap().title, "Editor"); + assert_eq!( + refs.resolve(&format!("id={window_id}")).unwrap().title, + "Editor" + ); } #[test] diff --git a/src/daemon/handler.rs b/src/daemon/handler.rs index d37b0f1..21f5e76 100644 --- a/src/daemon/handler.rs +++ b/src/daemon/handler.rs @@ -394,14 +394,13 @@ fn capture_snapshot( ) -> Result { let windows = refresh_windows(state)?; let screenshot_path = path.unwrap_or_else(temp_screenshot_path); - let screenshot = capture_and_save_screenshot( - state, - &screenshot_path, - annotate, - Some(&windows), - )?; + let screenshot = + capture_and_save_screenshot(state, &screenshot_path, annotate, Some(&windows))?; - Ok(Snapshot { screenshot, windows }) + Ok(Snapshot { + screenshot, + windows, + }) } fn capture_and_save_screenshot( @@ -439,55 +438,3 @@ fn parse_coords(value: &str) -> Option<(i32, i32)> { let y = parts[1].trim().parse().ok()?; Some((x, y)) } - -#[cfg(all(test, target_os = "linux"))] -mod tests { - use std::sync::Arc; - - use tokio::runtime::Builder; - use tokio::sync::Mutex; - - use super::handle_request; - use crate::core::protocol::Request; - use crate::daemon::state::DaemonState; - use crate::test_support::{X11TestEnv, deskctl_tmp_screenshot_count, env_lock}; - - #[test] - fn list_windows_is_side_effect_free_under_xvfb() { - let _guard = env_lock().lock().unwrap(); - let Some(env) = X11TestEnv::new().unwrap() else { - eprintln!("Skipping Xvfb-dependent list-windows test"); - return; - }; - env.create_window("deskctl list-windows test", "DeskctlList").unwrap(); - - let before = deskctl_tmp_screenshot_count(); - let runtime = Builder::new_current_thread().enable_all().build().unwrap(); - let state = Arc::new(Mutex::new( - DaemonState::new( - "test".to_string(), - std::env::temp_dir().join("deskctl-list-windows.sock"), - ) - .unwrap(), - )); - - let response = runtime.block_on(handle_request(&Request::new("list-windows"), &state)); - assert!(response.success); - - let data = response.data.unwrap(); - let windows = data - .get("windows") - .and_then(|value| value.as_array()) - .unwrap(); - assert!(windows.iter().any(|window| { - window - .get("title") - .and_then(|value| value.as_str()) - .map(|title| title == "deskctl list-windows test") - .unwrap_or(false) - })); - - let after = deskctl_tmp_screenshot_count(); - assert_eq!(before, after, "list-windows should not create screenshot artifacts"); - } -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..408a4fc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +pub mod backend; +pub mod cli; +pub mod core; +pub mod daemon; + +pub fn run() -> anyhow::Result<()> { + if std::env::var("DESKCTL_DAEMON").is_ok() { + return daemon::run(); + } + cli::run() +} diff --git a/src/main.rs b/src/main.rs index 4bb6fab..ed77595 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,3 @@ -mod backend; -mod cli; -mod core; -mod daemon; -#[cfg(test)] -mod test_support; - fn main() -> anyhow::Result<()> { - if std::env::var("DESKCTL_DAEMON").is_ok() { - return daemon::run(); - } - cli::run() + deskctl::run() } diff --git a/src/test_support.rs b/src/test_support.rs deleted file mode 100644 index c21a61c..0000000 --- a/src/test_support.rs +++ /dev/null @@ -1,150 +0,0 @@ -#![cfg(all(test, target_os = "linux"))] - -use std::path::Path; -use std::process::{Child, Command, Stdio}; -use std::sync::{Mutex, OnceLock}; -use std::thread; -use std::time::Duration; - -use anyhow::{Context, Result}; -use x11rb::connection::Connection; -use x11rb::protocol::xproto::{ - AtomEnum, ConnectionExt as XprotoConnectionExt, CreateWindowAux, EventMask, PropMode, - WindowClass, -}; - -pub fn env_lock() -> &'static Mutex<()> { - static LOCK: OnceLock> = OnceLock::new(); - LOCK.get_or_init(|| Mutex::new(())) -} - -pub struct X11TestEnv { - child: Child, - old_display: Option, - old_session_type: Option, -} - -impl X11TestEnv { - pub fn new() -> Result> { - if Command::new("Xvfb") - .arg("-help") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .is_err() - { - return Ok(None); - } - - for display_num in 90..110 { - let display = format!(":{display_num}"); - let lock_path = format!("/tmp/.X{display_num}-lock"); - let unix_socket = format!("/tmp/.X11-unix/X{display_num}"); - if Path::new(&lock_path).exists() || Path::new(&unix_socket).exists() { - continue; - } - - let child = Command::new("Xvfb") - .arg(&display) - .arg("-screen") - .arg("0") - .arg("1024x768x24") - .arg("-nolisten") - .arg("tcp") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .with_context(|| format!("Failed to launch Xvfb on {display}"))?; - - thread::sleep(Duration::from_millis(250)); - - let old_display = std::env::var("DISPLAY").ok(); - let old_session_type = std::env::var("XDG_SESSION_TYPE").ok(); - std::env::set_var("DISPLAY", &display); - std::env::set_var("XDG_SESSION_TYPE", "x11"); - - return Ok(Some(Self { - child, - old_display, - old_session_type, - })); - } - - anyhow::bail!("Failed to find a free Xvfb display") - } - - pub fn create_window(&self, title: &str, app_class: &str) -> Result<()> { - let (conn, screen_num) = - x11rb::connect(None).context("Failed to connect to test Xvfb display")?; - let screen = &conn.setup().roots[screen_num]; - let window = conn.generate_id()?; - - conn.create_window( - x11rb::COPY_DEPTH_FROM_PARENT, - window, - screen.root, - 10, - 10, - 320, - 180, - 0, - WindowClass::INPUT_OUTPUT, - 0, - &CreateWindowAux::new() - .background_pixel(screen.white_pixel) - .event_mask(EventMask::EXPOSURE), - )?; - conn.change_property8( - PropMode::REPLACE, - window, - AtomEnum::WM_NAME, - AtomEnum::STRING, - title.as_bytes(), - )?; - let class_bytes = format!("{app_class}\0{app_class}\0"); - conn.change_property8( - PropMode::REPLACE, - window, - AtomEnum::WM_CLASS, - AtomEnum::STRING, - class_bytes.as_bytes(), - )?; - conn.map_window(window)?; - conn.flush()?; - - thread::sleep(Duration::from_millis(150)); - Ok(()) - } -} - -impl Drop for X11TestEnv { - fn drop(&mut self) { - let _ = self.child.kill(); - let _ = self.child.wait(); - - match &self.old_display { - Some(value) => std::env::set_var("DISPLAY", value), - None => std::env::remove_var("DISPLAY"), - } - - match &self.old_session_type { - Some(value) => std::env::set_var("XDG_SESSION_TYPE", value), - None => std::env::remove_var("XDG_SESSION_TYPE"), - } - } -} - -pub fn deskctl_tmp_screenshot_count() -> usize { - std::fs::read_dir("/tmp") - .ok() - .into_iter() - .flat_map(|iter| iter.filter_map(Result::ok)) - .filter(|entry| { - entry - .file_name() - .to_str() - .map(|name| name.starts_with("deskctl-") && name.ends_with(".png")) - .unwrap_or(false) - }) - .count() -} diff --git a/tests/support/mod.rs b/tests/support/mod.rs new file mode 100644 index 0000000..d8b93a1 --- /dev/null +++ b/tests/support/mod.rs @@ -0,0 +1,220 @@ +#![cfg(target_os = "linux")] + +use std::os::unix::net::UnixListener; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; +use std::sync::{Mutex, OnceLock}; +use std::time::{SystemTime, UNIX_EPOCH}; + +use anyhow::{anyhow, bail, Context, Result}; +use deskctl::cli::{connection, GlobalOpts}; +use x11rb::connection::Connection; +use x11rb::protocol::xproto::{ + AtomEnum, ConnectionExt as XprotoConnectionExt, CreateWindowAux, EventMask, PropMode, + WindowClass, +}; +use x11rb::rust_connection::RustConnection; +use x11rb::wrapper::ConnectionExt as X11WrapperConnectionExt; + +pub fn env_lock() -> &'static Mutex<()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) +} + +pub struct SessionEnvGuard { + old_session_type: Option, +} + +impl SessionEnvGuard { + pub fn prepare() -> Option { + let _display = std::env::var("DISPLAY") + .ok() + .filter(|value| !value.is_empty())?; + + let old_session_type = std::env::var("XDG_SESSION_TYPE").ok(); + std::env::set_var("XDG_SESSION_TYPE", "x11"); + Some(Self { old_session_type }) + } +} + +impl Drop for SessionEnvGuard { + fn drop(&mut self) { + match &self.old_session_type { + Some(value) => std::env::set_var("XDG_SESSION_TYPE", value), + None => std::env::remove_var("XDG_SESSION_TYPE"), + } + } +} + +pub struct FixtureWindow { + conn: RustConnection, + window: u32, +} + +impl FixtureWindow { + pub fn create(title: &str, app_class: &str) -> Result { + let (conn, screen_num) = + x11rb::connect(None).context("Failed to connect to the integration test display")?; + let screen = &conn.setup().roots[screen_num]; + let window = conn.generate_id()?; + + conn.create_window( + x11rb::COPY_DEPTH_FROM_PARENT, + window, + screen.root, + 10, + 10, + 320, + 180, + 0, + WindowClass::INPUT_OUTPUT, + 0, + &CreateWindowAux::new() + .background_pixel(screen.white_pixel) + .event_mask(EventMask::EXPOSURE), + )?; + conn.change_property8( + PropMode::REPLACE, + window, + AtomEnum::WM_NAME, + AtomEnum::STRING, + title.as_bytes(), + )?; + let class_bytes = format!("{app_class}\0{app_class}\0"); + conn.change_property8( + PropMode::REPLACE, + window, + AtomEnum::WM_CLASS, + AtomEnum::STRING, + class_bytes.as_bytes(), + )?; + conn.map_window(window)?; + conn.flush()?; + + std::thread::sleep(std::time::Duration::from_millis(150)); + Ok(Self { conn, window }) + } +} + +impl Drop for FixtureWindow { + fn drop(&mut self) { + let _ = self.conn.destroy_window(self.window); + let _ = self.conn.flush(); + } +} + +pub struct TestSession { + pub opts: GlobalOpts, + root: PathBuf, +} + +impl TestSession { + pub fn new(label: &str) -> Result { + let suffix = SystemTime::now() + .duration_since(UNIX_EPOCH) + .context("System clock is before the Unix epoch")? + .as_nanos(); + let root = std::env::temp_dir().join(format!("deskctl-{label}-{suffix}")); + std::fs::create_dir_all(&root) + .with_context(|| format!("Failed to create {}", root.display()))?; + + Ok(Self { + opts: GlobalOpts { + socket: Some(root.join("deskctl.sock")), + session: format!("{label}-{suffix}"), + json: false, + }, + root, + }) + } + + pub fn socket_path(&self) -> &Path { + self.opts + .socket + .as_deref() + .expect("TestSession always has an explicit socket path") + } + + pub fn create_stale_socket(&self) -> Result<()> { + let listener = UnixListener::bind(self.socket_path()) + .with_context(|| format!("Failed to bind {}", self.socket_path().display()))?; + drop(listener); + Ok(()) + } + + pub fn start_daemon_cli(&self) -> Result<()> { + let output = self.run_cli(["daemon", "start"])?; + if output.status.success() { + return Ok(()); + } + + bail!( + "deskctl daemon start failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + + pub fn run_cli(&self, args: I) -> Result + where + I: IntoIterator, + S: AsRef, + { + let socket = self.socket_path(); + let mut command = Command::new(env!("CARGO_BIN_EXE_deskctl")); + command + .arg("--socket") + .arg(socket) + .arg("--session") + .arg(&self.opts.session); + + for arg in args { + command.arg(arg.as_ref()); + } + + command.output().with_context(|| { + format!( + "Failed to run {} against {}", + env!("CARGO_BIN_EXE_deskctl"), + socket.display() + ) + }) + } +} + +impl Drop for TestSession { + fn drop(&mut self) { + let _ = connection::stop_daemon(&self.opts); + if self.socket_path().exists() { + let _ = std::fs::remove_file(self.socket_path()); + } + let _ = std::fs::remove_dir_all(&self.root); + } +} + +pub fn deskctl_tmp_screenshot_count() -> usize { + std::fs::read_dir("/tmp") + .ok() + .into_iter() + .flat_map(|iter| iter.filter_map(Result::ok)) + .filter(|entry| { + entry + .file_name() + .to_str() + .map(|name| name.starts_with("deskctl-") && name.ends_with(".png")) + .unwrap_or(false) + }) + .count() +} + +pub fn successful_json_response(output: Output) -> Result { + if !output.status.success() { + return Err(anyhow!( + "deskctl command failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + + serde_json::from_slice(&output.stdout).context("Failed to parse JSON output from deskctl") +} diff --git a/tests/x11_runtime.rs b/tests/x11_runtime.rs new file mode 100644 index 0000000..ef09411 --- /dev/null +++ b/tests/x11_runtime.rs @@ -0,0 +1,115 @@ +#![cfg(target_os = "linux")] + +mod support; + +use anyhow::Result; +use deskctl::cli::connection::send_command; +use deskctl::core::doctor; +use deskctl::core::protocol::Request; + +use self::support::{ + deskctl_tmp_screenshot_count, env_lock, successful_json_response, FixtureWindow, + SessionEnvGuard, TestSession, +}; + +#[test] +fn doctor_reports_healthy_x11_environment() -> Result<()> { + let _guard = env_lock().lock().unwrap(); + let Some(_env) = SessionEnvGuard::prepare() else { + eprintln!("Skipping X11 integration test because DISPLAY is not set"); + return Ok(()); + }; + + let _window = FixtureWindow::create("deskctl doctor test", "DeskctlDoctor")?; + let session = TestSession::new("doctor")?; + let report = doctor::run(session.socket_path()); + + assert!(report + .checks + .iter() + .any(|check| check.name == "display" && check.ok)); + assert!(report + .checks + .iter() + .any(|check| check.name == "backend" && check.ok)); + assert!(report + .checks + .iter() + .any(|check| check.name == "window-enumeration" && check.ok)); + assert!(report + .checks + .iter() + .any(|check| check.name == "screenshot" && check.ok)); + + Ok(()) +} + +#[test] +fn list_windows_is_side_effect_free() -> Result<()> { + let _guard = env_lock().lock().unwrap(); + let Some(_env) = SessionEnvGuard::prepare() else { + eprintln!("Skipping X11 integration test because DISPLAY is not set"); + return Ok(()); + }; + + let _window = FixtureWindow::create("deskctl list-windows test", "DeskctlList")?; + let session = TestSession::new("list-windows")?; + session.start_daemon_cli()?; + + let before = deskctl_tmp_screenshot_count(); + let response = send_command(&session.opts, &Request::new("list-windows"))?; + assert!(response.success); + + let windows = response + .data + .and_then(|data| data.get("windows").cloned()) + .and_then(|windows| windows.as_array().cloned()) + .expect("list-windows response must include a windows array"); + assert!(windows.iter().any(|window| { + window + .get("title") + .and_then(|value| value.as_str()) + .map(|title| title == "deskctl list-windows test") + .unwrap_or(false) + })); + + let after = deskctl_tmp_screenshot_count(); + assert_eq!( + before, after, + "list-windows should not create screenshot artifacts" + ); + + Ok(()) +} + +#[test] +fn daemon_start_recovers_from_stale_socket() -> Result<()> { + let _guard = env_lock().lock().unwrap(); + let Some(_env) = SessionEnvGuard::prepare() else { + eprintln!("Skipping X11 integration test because DISPLAY is not set"); + return Ok(()); + }; + + let _window = FixtureWindow::create("deskctl daemon recovery test", "DeskctlDaemon")?; + let session = TestSession::new("daemon-recovery")?; + session.create_stale_socket()?; + + session.start_daemon_cli()?; + let response = successful_json_response(session.run_cli(["--json", "list-windows"])?) + .expect("list-windows should return valid JSON"); + + let windows = response + .get("data") + .and_then(|data| data.get("windows")) + .and_then(|value| value.as_array()) + .expect("CLI JSON response must include windows"); + assert!(windows.iter().any(|window| { + window + .get("title") + .and_then(|value| value.as_str()) + .map(|title| title == "deskctl daemon recovery test") + .unwrap_or(false) + })); + + Ok(()) +} From ec9bc1f137db77657840d2ae0ccda1ca5f805488 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 25 Mar 2026 19:30:22 -0400 Subject: [PATCH 18/64] rm ai stuff from git --- .claude/commands/opsx/apply.md | 152 --------- .claude/commands/opsx/archive.md | 157 ---------- .claude/commands/opsx/explore.md | 173 ----------- .claude/commands/opsx/propose.md | 106 ------- .claude/skills/openspec-apply-change/SKILL.md | 156 ---------- .../skills/openspec-archive-change/SKILL.md | 114 ------- .claude/skills/openspec-explore/SKILL.md | 288 ------------------ .claude/skills/openspec-propose/SKILL.md | 110 ------- .codex/skills/openspec-apply-change/SKILL.md | 156 ---------- .../skills/openspec-archive-change/SKILL.md | 114 ------- .codex/skills/openspec-explore/SKILL.md | 288 ------------------ .codex/skills/openspec-propose/SKILL.md | 110 ------- .gitignore | 3 + .../design.md | 116 ------- .../proposal.md | 28 -- .../specs/desktop-runtime/spec.md | 54 ---- .../tasks.md | 17 -- openspec/config.yaml | 20 -- openspec/specs/desktop-runtime/spec.md | 54 ---- 19 files changed, 3 insertions(+), 2213 deletions(-) delete mode 100644 .claude/commands/opsx/apply.md delete mode 100644 .claude/commands/opsx/archive.md delete mode 100644 .claude/commands/opsx/explore.md delete mode 100644 .claude/commands/opsx/propose.md delete mode 100644 .claude/skills/openspec-apply-change/SKILL.md delete mode 100644 .claude/skills/openspec-archive-change/SKILL.md delete mode 100644 .claude/skills/openspec-explore/SKILL.md delete mode 100644 .claude/skills/openspec-propose/SKILL.md delete mode 100644 .codex/skills/openspec-apply-change/SKILL.md delete mode 100644 .codex/skills/openspec-archive-change/SKILL.md delete mode 100644 .codex/skills/openspec-explore/SKILL.md delete mode 100644 .codex/skills/openspec-propose/SKILL.md delete mode 100644 openspec/changes/archive/2026-03-25-stabilize-v0-2-foundation/design.md delete mode 100644 openspec/changes/archive/2026-03-25-stabilize-v0-2-foundation/proposal.md delete mode 100644 openspec/changes/archive/2026-03-25-stabilize-v0-2-foundation/specs/desktop-runtime/spec.md delete mode 100644 openspec/changes/archive/2026-03-25-stabilize-v0-2-foundation/tasks.md delete mode 100644 openspec/config.yaml delete mode 100644 openspec/specs/desktop-runtime/spec.md diff --git a/.claude/commands/opsx/apply.md b/.claude/commands/opsx/apply.md deleted file mode 100644 index bf23721..0000000 --- a/.claude/commands/opsx/apply.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -name: "OPSX: Apply" -description: Implement tasks from an OpenSpec change (Experimental) -category: Workflow -tags: [workflow, artifacts, experimental] ---- - -Implement tasks from an OpenSpec change. - -**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. - -**Steps** - -1. **Select the change** - - If a name is provided, use it. Otherwise: - - Infer from conversation context if the user mentioned a change - - Auto-select if only one active change exists - - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select - - Always announce: "Using change: " and how to override (e.g., `/opsx:apply `). - -2. **Check status to understand the schema** - ```bash - openspec status --change "" --json - ``` - Parse the JSON to understand: - - `schemaName`: The workflow being used (e.g., "spec-driven") - - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) - -3. **Get apply instructions** - - ```bash - openspec instructions apply --change "" --json - ``` - - This returns: - - Context file paths (varies by schema) - - Progress (total, complete, remaining) - - Task list with status - - Dynamic instruction based on current state - - **Handle states:** - - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue` - - If `state: "all_done"`: congratulate, suggest archive - - Otherwise: proceed to implementation - -4. **Read context files** - - Read the files listed in `contextFiles` from the apply instructions output. - The files depend on the schema being used: - - **spec-driven**: proposal, specs, design, tasks - - Other schemas: follow the contextFiles from CLI output - -5. **Show current progress** - - Display: - - Schema being used - - Progress: "N/M tasks complete" - - Remaining tasks overview - - Dynamic instruction from CLI - -6. **Implement tasks (loop until done or blocked)** - - For each pending task: - - Show which task is being worked on - - Make the code changes required - - Keep changes minimal and focused - - Mark task complete in the tasks file: `- [ ]` → `- [x]` - - Continue to next task - - **Pause if:** - - Task is unclear → ask for clarification - - Implementation reveals a design issue → suggest updating artifacts - - Error or blocker encountered → report and wait for guidance - - User interrupts - -7. **On completion or pause, show status** - - Display: - - Tasks completed this session - - Overall progress: "N/M tasks complete" - - If all done: suggest archive - - If paused: explain why and wait for guidance - -**Output During Implementation** - -``` -## Implementing: (schema: ) - -Working on task 3/7: -[...implementation happening...] -✓ Task complete - -Working on task 4/7: -[...implementation happening...] -✓ Task complete -``` - -**Output On Completion** - -``` -## Implementation Complete - -**Change:** -**Schema:** -**Progress:** 7/7 tasks complete ✓ - -### Completed This Session -- [x] Task 1 -- [x] Task 2 -... - -All tasks complete! You can archive this change with `/opsx:archive`. -``` - -**Output On Pause (Issue Encountered)** - -``` -## Implementation Paused - -**Change:** -**Schema:** -**Progress:** 4/7 tasks complete - -### Issue Encountered - - -**Options:** -1.
diff --git a/site/src/pages/installation.mdx b/site/src/pages/installation.mdx index e05772d..985cf99 100644 --- a/site/src/pages/installation.mdx +++ b/site/src/pages/installation.mdx @@ -6,43 +6,68 @@ toc: true # Installation -## Cargo +## Default install ```sh -cargo install deskctl +npm install -g deskctl-cli +deskctl --help ``` -## From source +`deskctl-cli` is the default install path. It installs the `deskctl` command by +downloading the matching GitHub Release asset for the supported runtime target. + +## One-shot usage + +```sh +npx deskctl-cli --help +``` + +## Agent skill + +For `skills.sh`-style runtimes: + +```sh +npx skills add harivansh-afk/deskctl -s deskctl +``` + +The repo skill lives under `skills/deskctl` and is designed around the same +observe -> wait -> act -> verify loop as the CLI. + +## Other install paths + +### Nix + +```sh +nix run github:harivansh-afk/deskctl -- --help +nix profile install github:harivansh-afk/deskctl +``` + +### Build from source ```sh git clone https://github.com/harivansh-afk/deskctl cd deskctl -cargo build --release +cargo build ``` -## Docker (cross-compile for Linux) +Source builds on Linux require: -Build a static Linux binary from any platform: +- Rust 1.75+ +- `pkg-config` +- X11 development libraries such as `libx11-dev` and `libxtst-dev` -```sh -docker compose -f docker/docker-compose.yml run --rm build -``` - -This writes `dist/deskctl-linux-x86_64`. - -## Deploy to a remote machine - -Copy the binary over SSH when `scp` is not available: - -```sh -ssh -p 443 user@host 'cat > ~/deskctl && chmod +x ~/deskctl' < dist/deskctl-linux-x86_64 -``` - -## Requirements +## Runtime requirements - Linux with an active X11 session -- `DISPLAY` environment variable set (e.g. `DISPLAY=:1`) -- `XDG_SESSION_TYPE=x11` -- A window manager that exposes EWMH properties (`_NET_CLIENT_LIST_STACKING`, `_NET_ACTIVE_WINDOW`) +- `DISPLAY` set to a usable X11 display, such as `DISPLAY=:1` +- `XDG_SESSION_TYPE=x11` or an equivalent X11 session environment +- a window manager or desktop environment that exposes standard EWMH properties + such as `_NET_CLIENT_LIST_STACKING` and `_NET_ACTIVE_WINDOW` -No extra native libraries are needed beyond the standard glibc runtime (`libc`, `libm`, `libgcc_s`). +The binary itself only depends on the standard Linux glibc runtime. + +If setup fails, run: + +```sh +deskctl doctor +``` diff --git a/site/src/pages/quick-start.mdx b/site/src/pages/quick-start.mdx index 7f3bc07..c783b9e 100644 --- a/site/src/pages/quick-start.mdx +++ b/site/src/pages/quick-start.mdx @@ -6,50 +6,72 @@ toc: true # Quick start -## Core workflow - -The typical agent loop is: snapshot the desktop, interpret the result, act on it. +## Install and diagnose ```sh -# 1. see the desktop -deskctl --json snapshot --annotate +npm install -g deskctl-cli +deskctl doctor +``` -# 2. click a window by its ref -deskctl click @w1 +Use `deskctl doctor` first. It checks X11 connectivity, basic enumeration, +screenshot viability, and socket health before you start driving the desktop. -# 3. type into the focused window -deskctl type "hello world" +## Observe -# 4. press a key +```sh +deskctl snapshot --annotate +deskctl list-windows +deskctl get active-window +deskctl get monitors +``` + +Use `snapshot` when you want a screenshot artifact plus window refs. Use +`list-windows` when you only need the current window tree without writing a +screenshot. + +## Target windows cleanly + +Prefer explicit selectors when you need deterministic targeting: + +```sh +ref=w1 +id=win1 +title=Firefox +class=firefox +focused +``` + +Legacy refs such as `@w1` still work after `snapshot` or `list-windows`. Bare +strings like `firefox` are fuzzy matches and now fail on ambiguity. + +## Wait, act, verify + +The core loop is: + +```sh +# observe +deskctl snapshot --annotate + +# wait +deskctl wait window --selector 'title=Firefox' --timeout 10 + +# act +deskctl focus 'title=Firefox' +deskctl hotkey ctrl l +deskctl type "https://example.com" deskctl press enter + +# verify +deskctl wait focus --selector 'title=Firefox' --timeout 5 +deskctl snapshot ``` -The `--annotate` flag draws colored bounding boxes and `@wN` labels on the screenshot so agents can visually identify windows. +The wait commands return the matched window payload on success, so they compose +cleanly into the next action. -## Window refs +## Use `--json` when parsing matters -Every `snapshot` assigns refs like `@w1`, `@w2`, etc. to each visible window, ordered top-to-bottom by stacking order. Use these refs anywhere a selector is expected: - -```sh -deskctl click @w1 -deskctl focus @w3 -deskctl close @w2 -``` - -You can also select windows by name (case-insensitive substring match): - -```sh -deskctl focus "firefox" -deskctl close "terminal" -``` - -## JSON output - -Pass `--json` for machine-readable output. This is the primary mode for agent integrations: - -```sh -deskctl --json snapshot -``` +Every command supports `--json` and uses the same top-level envelope: ```json { @@ -59,7 +81,7 @@ deskctl --json snapshot "windows": [ { "ref_id": "w1", - "xcb_id": 12345678, + "window_id": "win1", "title": "Firefox", "app_name": "firefox", "x": 0, @@ -74,14 +96,8 @@ deskctl --json snapshot } ``` -## Daemon lifecycle +Use `window_id` for stable targeting inside a live daemon session. The exact +text formatting is intentionally compact, but JSON is the parsing contract. -The daemon starts automatically on the first command. It keeps the X11 connection alive so repeated calls are fast. You do not need to manage it manually. - -```sh -# check if the daemon is running -deskctl daemon status - -# stop it explicitly -deskctl daemon stop -``` +The full stable-vs-best-effort contract lives on the +[runtime contract](/runtime-contract) page. diff --git a/site/src/pages/runtime-contract.mdx b/site/src/pages/runtime-contract.mdx new file mode 100644 index 0000000..4fca14c --- /dev/null +++ b/site/src/pages/runtime-contract.mdx @@ -0,0 +1,177 @@ +--- +layout: ../layouts/DocLayout.astro +title: Runtime contract +toc: true +--- + +# Runtime contract + +This page defines the current public output contract for `deskctl`. + +It is intentionally scoped to the current Linux X11 runtime surface. It does +not promise stability for future Wayland or window-manager-specific features. + +## JSON envelope + +Every command supports `--json` and uses the same top-level envelope: + +```json +{ + "success": true, + "data": {}, + "error": null +} +``` + +Stable top-level fields: + +- `success` +- `data` +- `error` + +If `success` is `false`, the command exits non-zero in both text mode and JSON +mode. + +## Stable window fields + +Whenever a response includes a window payload, these fields are stable: + +- `ref_id` +- `window_id` +- `title` +- `app_name` +- `x` +- `y` +- `width` +- `height` +- `focused` +- `minimized` + +`window_id` is the public session-scoped identifier for programmatic targeting. +`ref_id` is a short-lived convenience handle from the current ref map. + +## Stable grouped reads + +`deskctl get active-window` + +- stable: `data.window` + +`deskctl get monitors` + +- stable: `data.count` +- stable: `data.monitors` + +Stable per-monitor fields: + +- `name` +- `x` +- `y` +- `width` +- `height` +- `width_mm` +- `height_mm` +- `primary` +- `automatic` + +`deskctl get version` + +- stable: `data.version` +- stable: `data.backend` + +`deskctl get systeminfo` + +- stable: `data.backend` +- stable: `data.display` +- stable: `data.session_type` +- stable: `data.session` +- stable: `data.socket_path` +- stable: `data.screen` +- stable: `data.monitor_count` +- stable: `data.monitors` + +## Stable waits + +`deskctl wait window` +`deskctl wait focus` + +- stable: `data.wait` +- stable: `data.selector` +- stable: `data.elapsed_ms` +- stable: `data.window` + +## Stable selector-driven action fields + +When selector-driven actions return resolved window data, these fields are +stable when present: + +- `data.ref_id` +- `data.window_id` +- `data.title` +- `data.selector` + +This applies to: + +- `click` +- `dblclick` +- `focus` +- `close` +- `move-window` +- `resize-window` + +## Stable artifact fields + +For `snapshot` and `screenshot`: + +- stable: `data.screenshot` + +When a command also returns windows, `data.windows` uses the stable window +payload documented above. + +## Stable structured error kinds + +When a command fails with structured JSON data, these error kinds are stable: + +- `selector_not_found` +- `selector_ambiguous` +- `selector_invalid` +- `timeout` +- `not_found` +- `window_not_focused` in `data.last_observation.kind` or an equivalent wait + observation payload + +Stable structured failure fields include: + +- `data.kind` +- `data.selector` +- `data.mode` +- `data.candidates` +- `data.message` +- `data.wait` +- `data.timeout_ms` +- `data.poll_ms` +- `data.last_observation` + +## Best-effort fields + +These values are useful but environment-dependent and should not be treated as +strict parsing guarantees: + +- exact monitor naming conventions +- EWMH/window-manager-dependent ordering details +- cosmetic text formatting in non-JSON mode +- default screenshot file names when no explicit path was provided +- stderr wording outside the structured `kind` classifications above + +## Text mode expectations + +Text mode is intended to stay compact and follow-up-useful. + +The exact whitespace and alignment are not stable. The stable behavioral +expectations are: + +- important reads print actionable identifiers or geometry +- selector failures print enough detail to recover without `--json` +- artifact-producing commands print the artifact path +- window listings print both `@wN` refs and `window_id` values + +If you need strict parsing, use `--json`. diff --git a/site/src/styles/base.css b/site/src/styles/base.css index 86fd6a8..f60c0e6 100644 --- a/site/src/styles/base.css +++ b/site/src/styles/base.css @@ -65,6 +65,23 @@ main { font-style: italic; } +.lede { + font-size: 1.05rem; + max-width: 42rem; +} + +.badges { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin-bottom: 1.25rem; +} + +.badges a, +.badges img { + display: block; +} + header { display: flex; align-items: center; @@ -117,6 +134,10 @@ a:hover { text-decoration-thickness: 2px; } +img { + max-width: 100%; +} + ul, ol { padding-left: 1.25em; diff --git a/skills/deskctl/references/commands.md b/skills/deskctl/references/commands.md index d0e7c9f..77b9513 100644 --- a/skills/deskctl/references/commands.md +++ b/skills/deskctl/references/commands.md @@ -1,21 +1,22 @@ # deskctl commands -All commands support `--json` for machine-parseable output following the runtime contract. +All commands support `--json` for machine-parseable output following the +runtime contract. ## Observe ```bash -deskctl doctor # check X11 runtime and daemon health -deskctl snapshot # screenshot + window list -deskctl snapshot --annotate # screenshot with @wN labels overlaid -deskctl list-windows # window list only (no screenshot) -deskctl screenshot /tmp/screen.png # screenshot to explicit path -deskctl get active-window # focused window info -deskctl get monitors # monitor geometry -deskctl get version # version and backend -deskctl get systeminfo # full runtime diagnostics -deskctl get-screen-size # screen resolution -deskctl get-mouse-position # cursor coordinates +deskctl doctor +deskctl snapshot +deskctl snapshot --annotate +deskctl list-windows +deskctl screenshot /tmp/screen.png +deskctl get active-window +deskctl get monitors +deskctl get version +deskctl get systeminfo +deskctl get-screen-size +deskctl get-mouse-position ``` ## Wait @@ -25,19 +26,21 @@ deskctl wait window --selector 'title=Firefox' --timeout 10 deskctl wait focus --selector 'class=firefox' --timeout 5 ``` -Returns the matched window payload on success. Failures include structured `kind` values in `--json` mode. +Returns the matched window payload on success. Failures include structured +`kind` values in `--json` mode. ## Selectors ```bash -ref=w1 # snapshot ref (short-lived, from last snapshot) -id=win1 # stable window ID (session-scoped) -title=Firefox # match by window title -class=firefox # match by WM class -focused # currently focused window +ref=w1 +id=win1 +title=Firefox +class=firefox +focused ``` -Legacy shorthand: `@w1`, `w1`, `win1`. Bare strings do fuzzy matching but fail on ambiguity. +Legacy shorthand: `@w1`, `w1`, `win1`. Bare strings do fuzzy matching but fail +on ambiguity. ## Act @@ -58,12 +61,5 @@ deskctl close @w3 deskctl launch firefox ``` -## Daemon - -```bash -deskctl daemon start -deskctl daemon stop -deskctl daemon status -``` - -The daemon starts automatically on first command. Manual control is rarely needed. +The daemon starts automatically on first command. In normal usage you should +not need to manage it directly. From 88f9ff85a3fa5b95028bb1e7811078416eaf43ae Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 08:18:57 -0400 Subject: [PATCH 33/64] clean --- site/src/pages/index.astro | 25 ------------------------- site/src/styles/base.css | 12 ------------ 2 files changed, 37 deletions(-) diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 4263549..b8bf92b 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -10,31 +10,6 @@ import DocLayout from "../layouts/DocLayout.astro";

non-interactive desktop control for AI agents

- -

deskctl is a thin X11 control primitive for agent loops: diagnose the runtime, observe the desktop, wait for state transitions, act deterministically, diff --git a/site/src/styles/base.css b/site/src/styles/base.css index f60c0e6..cd569a9 100644 --- a/site/src/styles/base.css +++ b/site/src/styles/base.css @@ -70,18 +70,6 @@ main { max-width: 42rem; } -.badges { - display: flex; - flex-wrap: wrap; - gap: 0.6rem; - margin-bottom: 1.25rem; -} - -.badges a, -.badges img { - display: block; -} - header { display: flex; align-items: center; From eac3a61ceb35002bf3957e6d0ebe4c2025ab1203 Mon Sep 17 00:00:00 2001 From: Hari <73809867+harivansh-afk@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:44:10 -0400 Subject: [PATCH 34/64] rename (#11) * align docs and contract * clean * rename from deskctl-cli to deskctl * runtime --- .github/workflows/ci.yml | 4 ++-- .github/workflows/publish.yml | 12 ++++++------ .gitignore | 4 ++-- CONTRIBUTING.md | 2 +- Makefile | 4 ++-- README.md | 9 +++++---- docs/releasing.md | 4 ++-- docs/runtime-contract.md | 2 -- npm/{deskctl-cli => deskctl}/README.md | 10 +++++----- npm/{deskctl-cli => deskctl}/bin/deskctl.js | 2 +- npm/{deskctl-cli => deskctl}/package.json | 4 ++-- npm/{deskctl-cli => deskctl}/scripts/postinstall.js | 2 +- npm/{deskctl-cli => deskctl}/scripts/support.js | 2 +- .../scripts/validate-package.js | 4 ++-- site/src/pages/index.astro | 11 +++-------- site/src/pages/installation.mdx | 6 +++--- site/src/pages/quick-start.mdx | 2 +- skills/deskctl/SKILL.md | 4 ++-- 18 files changed, 41 insertions(+), 47 deletions(-) rename npm/{deskctl-cli => deskctl}/README.md (67%) rename npm/{deskctl-cli => deskctl}/bin/deskctl.js (91%) rename npm/{deskctl-cli => deskctl}/package.json (86%) rename npm/{deskctl-cli => deskctl}/scripts/postinstall.js (94%) rename npm/{deskctl-cli => deskctl}/scripts/support.js (97%) rename npm/{deskctl-cli => deskctl}/scripts/validate-package.js (87%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e95b27a..b7a4d6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,7 +189,7 @@ jobs: NEW="${{ needs.changes.outputs.version }}" if [ "$CURRENT" != "$NEW" ]; then sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml - node -e 'const fs=require("node:fs"); const path="npm/deskctl-cli/package.json"; const pkg=JSON.parse(fs.readFileSync(path,"utf8")); pkg.version=process.argv[1]; fs.writeFileSync(path, JSON.stringify(pkg, null, 2)+"\n");' "$NEW" + node -e 'const fs=require("node:fs"); const path="npm/deskctl/package.json"; const pkg=JSON.parse(fs.readFileSync(path,"utf8")); pkg.version=process.argv[1]; fs.writeFileSync(path, JSON.stringify(pkg, null, 2)+"\n");' "$NEW" cargo generate-lockfile fi @@ -199,7 +199,7 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" if ! git diff --quiet; then - git add Cargo.toml Cargo.lock npm/deskctl-cli/package.json + git add Cargo.toml Cargo.lock npm/deskctl/package.json git commit -m "release: ${{ needs.changes.outputs.tag }} [skip ci]" fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 329f151..c4b1ecf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,7 +8,7 @@ on: required: true type: string publish_npm: - description: Publish deskctl-cli to npm + description: Publish deskctl to npm required: true type: boolean default: false @@ -51,7 +51,7 @@ jobs: TAG="${{ inputs.tag }}" VERSION="${TAG#v}" CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - NPM_VERSION=$(node -p 'require("./npm/deskctl-cli/package.json").version') + NPM_VERSION=$(node -p 'require("./npm/deskctl/package.json").version') test "$VERSION" = "$CARGO_VERSION" test "$VERSION" = "$NPM_VERSION" @@ -62,7 +62,7 @@ jobs: VERSION="${{ inputs.tag }}" VERSION="${VERSION#v}" - if npm view "deskctl-cli@${VERSION}" version >/dev/null 2>&1; then + if npm view "deskctl@${VERSION}" version >/dev/null 2>&1; then echo "npm=true" >> "$GITHUB_OUTPUT" else echo "npm=false" >> "$GITHUB_OUTPUT" @@ -77,8 +77,8 @@ jobs: - name: Validate npm package run: | mkdir -p ./tmp/npm-pack - node npm/deskctl-cli/scripts/validate-package.js - npm pack ./npm/deskctl-cli --pack-destination ./tmp/npm-pack >/dev/null + node npm/deskctl/scripts/validate-package.js + npm pack ./npm/deskctl --pack-destination ./tmp/npm-pack >/dev/null - name: Validate crate publish path run: cargo publish --dry-run --locked @@ -87,7 +87,7 @@ jobs: if: inputs.publish_npm && steps.published.outputs.npm != 'true' env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: npm publish ./npm/deskctl-cli --access public + run: npm publish ./npm/deskctl --access public - name: Publish crates.io if: inputs.publish_crates && steps.published.outputs.crates != 'true' diff --git a/.gitignore b/.gitignore index db552f7..40542a9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ secret/ .claude/ .codex/ openspec/ -npm/deskctl-cli/vendor/ -npm/deskctl-cli/*.tgz +npm/deskctl/vendor/ +npm/deskctl/*.tgz diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 926c58a..97e8c7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ The hook config intentionally stays small: Distribution support currently ships through: - crate: `deskctl` -- npm package: `deskctl-cli` +- npm package: `deskctl` - repo flake: `flake.nix` - command name on every channel: `deskctl` diff --git a/Makefile b/Makefile index 97857e3..7e1f852 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,10 @@ npm-package-check: echo "npm is required for npm packaging validation."; \ exit 1; \ fi - node npm/deskctl-cli/scripts/validate-package.js + node npm/deskctl/scripts/validate-package.js rm -rf tmp/npm-pack tmp/npm-install mkdir -p tmp/npm-pack tmp/npm-install/bin - npm pack ./npm/deskctl-cli --pack-destination ./tmp/npm-pack >/dev/null + npm pack ./npm/deskctl --pack-destination ./tmp/npm-pack >/dev/null @if [ "$$(uname -s)" != "Linux" ]; then \ echo "Skipping npm package runtime smoke test on non-Linux host."; \ else \ diff --git a/README.md b/README.md index 32144f0..4b42b5f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # deskctl -[![npm](https://img.shields.io/npm/v/deskctl-cli?label=npm)](https://www.npmjs.com/package/deskctl-cli) +[![npm](https://img.shields.io/npm/v/deskctl?label=npm)](https://www.npmjs.com/package/deskctl) [![release](https://img.shields.io/github/v/release/harivansh-afk/deskctl?label=release)](https://github.com/harivansh-afk/deskctl/releases) [![runtime](https://img.shields.io/badge/runtime-linux--x11-111827)](#support-boundary) [![skill](https://img.shields.io/badge/skills.sh-deskctl-111827)](skills/deskctl) @@ -10,7 +10,7 @@ Non-interactive desktop control for AI agents on Linux X11. ## Install ```bash -npm install -g deskctl-cli +npm install -g deskctl deskctl doctor deskctl snapshot --annotate ``` @@ -18,10 +18,11 @@ deskctl snapshot --annotate One-shot execution also works: ```bash -npx deskctl-cli --help +npx deskctl --help ``` -`deskctl-cli` installs the `deskctl` command by downloading the matching GitHub Release asset for the supported runtime target. +`deskctl` installs the command by downloading the matching GitHub Release asset for the supported runtime target. + ## Installable skill diff --git a/docs/releasing.md b/docs/releasing.md index 7271b83..8f39d3f 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -12,14 +12,14 @@ GitHub Releases are the canonical binary source. The npm package consumes those ## Package Names - crate: `deskctl` -- npm package: `deskctl-cli` +- npm package: `deskctl` - installed command: `deskctl` ## Prerequisites Before the first live publish on each registry: -- npm ownership for `deskctl-cli` +- npm ownership for `deskctl` - crates.io ownership for `deskctl` - repository secrets: - `NPM_TOKEN` diff --git a/docs/runtime-contract.md b/docs/runtime-contract.md index 0316c06..ee4727b 100644 --- a/docs/runtime-contract.md +++ b/docs/runtime-contract.md @@ -68,5 +68,3 @@ Treat these as useful but non-contractual: - incidental text formatting in non-JSON mode - default screenshot file names when no explicit path was provided - environment-dependent ordering details from the window manager - -For the full repo copy, see `docs/runtime-contract.md`. diff --git a/npm/deskctl-cli/README.md b/npm/deskctl/README.md similarity index 67% rename from npm/deskctl-cli/README.md rename to npm/deskctl/README.md index fd6f610..7bb42a9 100644 --- a/npm/deskctl-cli/README.md +++ b/npm/deskctl/README.md @@ -1,11 +1,11 @@ -# deskctl-cli +# deskctl -`deskctl-cli` installs the `deskctl` command for Linux X11 systems. +`deskctl` installs the command for Linux X11 systems. ## Install ```bash -npm install -g deskctl-cli +npm install -g deskctl ``` After install, run: @@ -17,7 +17,7 @@ deskctl --help One-shot usage is also supported: ```bash -npx deskctl-cli --help +npx deskctl --help ``` ## Runtime Support @@ -26,7 +26,7 @@ npx deskctl-cli --help - X11 session - currently packaged release asset: `linux-x64` -`deskctl-cli` downloads the matching GitHub Release binary during install. +`deskctl` downloads the matching GitHub Release binary during install. Unsupported targets fail during install with a clear runtime support error instead of installing a broken command. If you want the Rust source-install path instead, use: diff --git a/npm/deskctl-cli/bin/deskctl.js b/npm/deskctl/bin/deskctl.js similarity index 91% rename from npm/deskctl-cli/bin/deskctl.js rename to npm/deskctl/bin/deskctl.js index 9f9b480..b8514cf 100644 --- a/npm/deskctl-cli/bin/deskctl.js +++ b/npm/deskctl/bin/deskctl.js @@ -17,7 +17,7 @@ function main() { `Expected: ${binaryPath}`, `Package version: ${pkg.version}`, `Release tag: ${releaseTag(pkg)}`, - "Try reinstalling deskctl-cli or check that your target is supported." + "Try reinstalling deskctl or check that your target is supported." ].join("\n") ); process.exit(1); diff --git a/npm/deskctl-cli/package.json b/npm/deskctl/package.json similarity index 86% rename from npm/deskctl-cli/package.json rename to npm/deskctl/package.json index 84f27ee..4dbaba6 100644 --- a/npm/deskctl-cli/package.json +++ b/npm/deskctl/package.json @@ -1,7 +1,7 @@ { - "name": "deskctl-cli", + "name": "deskctl", "version": "0.1.6", - "description": "Installable deskctl CLI package for Linux X11 agents", + "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", "repository": { diff --git a/npm/deskctl-cli/scripts/postinstall.js b/npm/deskctl/scripts/postinstall.js similarity index 94% rename from npm/deskctl-cli/scripts/postinstall.js rename to npm/deskctl/scripts/postinstall.js index de1b1d0..1f43ad0 100644 --- a/npm/deskctl-cli/scripts/postinstall.js +++ b/npm/deskctl/scripts/postinstall.js @@ -44,6 +44,6 @@ async function main() { } main().catch((error) => { - console.error(`deskctl-cli install failed: ${error.message}`); + console.error(`deskctl install failed: ${error.message}`); process.exit(1); }); diff --git a/npm/deskctl-cli/scripts/support.js b/npm/deskctl/scripts/support.js similarity index 97% rename from npm/deskctl-cli/scripts/support.js rename to npm/deskctl/scripts/support.js index 8d41520..1fd0d47 100644 --- a/npm/deskctl-cli/scripts/support.js +++ b/npm/deskctl/scripts/support.js @@ -26,7 +26,7 @@ function supportedTarget(platform = process.platform, arch = process.arch) { } throw new Error( - `deskctl-cli currently supports linux-x64 only. Received ${platform}-${arch}.` + `deskctl currently supports linux-x64 only. Received ${platform}-${arch}.` ); } diff --git a/npm/deskctl-cli/scripts/validate-package.js b/npm/deskctl/scripts/validate-package.js similarity index 87% rename from npm/deskctl-cli/scripts/validate-package.js rename to npm/deskctl/scripts/validate-package.js index 46d3e87..450fd6c 100644 --- a/npm/deskctl-cli/scripts/validate-package.js +++ b/npm/deskctl/scripts/validate-package.js @@ -26,13 +26,13 @@ function main() { } if (pkg.bin?.deskctl !== "bin/deskctl.js") { - throw new Error("deskctl-cli must expose the deskctl bin entrypoint."); + throw new Error("deskctl must expose the deskctl bin entrypoint."); } const target = supportedTarget("linux", "x64"); const targetPath = vendorBinaryPath(target); const vendorDir = path.dirname(targetPath); - if (!vendorDir.endsWith(path.join("deskctl-cli", "vendor"))) { + if (!vendorDir.endsWith(path.join("deskctl", "vendor"))) { throw new Error("Vendor binary directory resolved unexpectedly."); } } diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index b8bf92b..8b8d4b4 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -16,10 +16,6 @@ import DocLayout from "../layouts/DocLayout.astro"; then verify.

-
npm install -g deskctl-cli
-deskctl doctor
-deskctl snapshot --annotate
-

Start here

    @@ -33,6 +29,7 @@ deskctl snapshot --annotate

    Agent skill

    @@ -47,15 +44,13 @@ deskctl snapshot --annotate diff --git a/site/src/pages/installation.mdx b/site/src/pages/installation.mdx index 985cf99..df53fcc 100644 --- a/site/src/pages/installation.mdx +++ b/site/src/pages/installation.mdx @@ -9,17 +9,17 @@ toc: true ## Default install ```sh -npm install -g deskctl-cli +npm install -g deskctl deskctl --help ``` -`deskctl-cli` is the default install path. It installs the `deskctl` command by +`deskctl` is the default install path. It installs the command by downloading the matching GitHub Release asset for the supported runtime target. ## One-shot usage ```sh -npx deskctl-cli --help +npx deskctl --help ``` ## Agent skill diff --git a/site/src/pages/quick-start.mdx b/site/src/pages/quick-start.mdx index c783b9e..10f3ec0 100644 --- a/site/src/pages/quick-start.mdx +++ b/site/src/pages/quick-start.mdx @@ -9,7 +9,7 @@ toc: true ## Install and diagnose ```sh -npm install -g deskctl-cli +npm install -g deskctl deskctl doctor ``` diff --git a/skills/deskctl/SKILL.md b/skills/deskctl/SKILL.md index 81dea19..244a1fb 100644 --- a/skills/deskctl/SKILL.md +++ b/skills/deskctl/SKILL.md @@ -1,7 +1,7 @@ --- name: deskctl description: Non-interactive X11 desktop control for AI agents. Use when the task involves controlling a Linux desktop - clicking, typing, reading windows, waiting for UI state, or taking screenshots inside a sandbox or VM. -allowed-tools: Bash(deskctl:*), Bash(npx deskctl-cli:*), Bash(npm:*), Bash(which:*), Bash(printenv:*), Bash(echo:*) +allowed-tools: Bash(deskctl:*), Bash(npx deskctl:*), Bash(npm:*), Bash(which:*), Bash(printenv:*), Bash(echo:*) --- # deskctl @@ -13,7 +13,7 @@ All output follows the runtime contract defined in [references/runtime-contract. ## Quick start ```bash -npm install -g deskctl-cli +npm install -g deskctl deskctl doctor deskctl snapshot --annotate ``` From 86c36a3b509aac8ea5869feb02df931fbcf7d752 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 08:53:50 -0400 Subject: [PATCH 35/64] release: v0.1.7 [skip ci] --- Cargo.lock | 2 +- Cargo.toml | 2 +- npm/deskctl/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71a9a54..6922004 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deskctl" -version = "0.1.6" +version = "0.1.7" dependencies = [ "ab_glyph", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index b05507b..5872639 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deskctl" -version = "0.1.6" +version = "0.1.7" edition = "2021" description = "X11 desktop control CLI for agents" license = "MIT" diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json index 4dbaba6..6085bca 100644 --- a/npm/deskctl/package.json +++ b/npm/deskctl/package.json @@ -1,6 +1,6 @@ { "name": "deskctl", - "version": "0.1.6", + "version": "0.1.7", "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", From 1d72c7b852e4195f20b002a4aaf25e2b1a2b8e26 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:01:12 -0400 Subject: [PATCH 36/64] fix: add registry-url to setup-node for npm auth [skip ci] --- .github/workflows/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c4b1ecf..1f6b282 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,6 +34,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 22 + registry-url: https://registry.npmjs.org - name: Install system dependencies run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev From deaffff45a574b1701482ac04043f7af557e46f5 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:07:56 -0400 Subject: [PATCH 37/64] major/minor/patch --- .github/workflows/publish.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1f6b282..31b3f4f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,15 +3,19 @@ name: Publish Registries on: workflow_dispatch: inputs: - tag: - description: Release tag to publish (for example v0.1.5) + bump: + description: Version bump type required: true - type: string + type: choice + options: + - patch + - minor + - major publish_npm: description: Publish deskctl to npm required: true type: boolean - default: false + default: true publish_crates: description: Publish deskctl to crates.io required: true From 47047e90641bf5e4b90f31aeb1157cd9b054868e Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:11:13 -0400 Subject: [PATCH 38/64] migrate update manifest job to publish workflow --- .github/workflows/ci.yml | 75 +++-------------------------------- .github/workflows/publish.yml | 54 +++++++++++++++++-------- 2 files changed, 43 insertions(+), 86 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7a4d6f..cb36e61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,32 +52,13 @@ jobs: echo "rust=${{ steps.filter.outputs.rust }}" >> "$GITHUB_OUTPUT" fi - - name: Calculate next version + - name: Read current version id: version if: github.event_name != 'pull_request' && steps.check.outputs.rust == 'true' run: | - BASE=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE" - - LATEST=$(git tag -l "v${MAJOR}.${MINOR}.*" | sort -V | tail -1) - - if [ -z "$LATEST" ]; then - NEW="$BASE" - else - LATEST_VER="${LATEST#v}" - IFS='.' read -r _ _ LATEST_PATCH <<< "$LATEST_VER" - NEW_PATCH=$((LATEST_PATCH + 1)) - NEW="${MAJOR}.${MINOR}.${NEW_PATCH}" - fi - - # Ensure the computed version does not already have a tag - while git rev-parse "v${NEW}" >/dev/null 2>&1; do - IFS='.' read -r MAJOR MINOR PATCH <<< "$NEW" - NEW="${MAJOR}.${MINOR}.$((PATCH + 1))" - done - - echo "version=${NEW}" >> "$GITHUB_OUTPUT" - echo "tag=v${NEW}" >> "$GITHUB_OUTPUT" + VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" validate: name: Validate @@ -167,57 +148,13 @@ jobs: - name: Distribution validation run: make dist-validate - update-manifests: - name: Update Manifests - needs: [changes, validate, integration, distribution] - if: github.event_name != 'pull_request' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: dtolnay/rust-toolchain@stable - - - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Update version in Cargo.toml - run: | - CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - NEW="${{ needs.changes.outputs.version }}" - if [ "$CURRENT" != "$NEW" ]; then - sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml - node -e 'const fs=require("node:fs"); const path="npm/deskctl/package.json"; const pkg=JSON.parse(fs.readFileSync(path,"utf8")); pkg.version=process.argv[1]; fs.writeFileSync(path, JSON.stringify(pkg, null, 2)+"\n");' "$NEW" - cargo generate-lockfile - fi - - - name: Commit, tag, and push - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - if ! git diff --quiet; then - git add Cargo.toml Cargo.lock npm/deskctl/package.json - git commit -m "release: ${{ needs.changes.outputs.tag }} [skip ci]" - fi - - if ! git rev-parse "${{ needs.changes.outputs.tag }}" >/dev/null 2>&1; then - git tag "${{ needs.changes.outputs.tag }}" - fi - git push origin main --tags - build: name: Build Release Asset - needs: [changes, update-manifests] + needs: [changes, validate, integration, distribution] if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - ref: ${{ needs.changes.outputs.tag }} - fetch-depth: 0 - uses: dtolnay/rust-toolchain@stable with: @@ -242,7 +179,7 @@ jobs: release: name: Release - needs: [changes, build, update-manifests] + needs: [changes, build] if: github.event_name != 'pull_request' runs-on: ubuntu-latest steps: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 31b3f4f..60aed4d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,10 +28,12 @@ permissions: jobs: publish: runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 with: - ref: ${{ inputs.tag }} + fetch-depth: 0 - uses: dtolnay/rust-toolchain@stable @@ -43,29 +45,46 @@ jobs: - name: Install system dependencies run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev - - name: Verify release exists and contains canonical assets - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Compute next version + id: version run: | - gh release view "${{ inputs.tag }}" --json assets --jq '.assets[].name' > /tmp/release-assets.txt - grep -Fx "deskctl-linux-x86_64" /tmp/release-assets.txt >/dev/null - grep -Fx "checksums.txt" /tmp/release-assets.txt >/dev/null + CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" - - name: Verify versions align with tag + case "${{ inputs.bump }}" in + major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; + minor) MINOR=$((MINOR + 1)); PATCH=0 ;; + patch) PATCH=$((PATCH + 1)) ;; + esac + + NEW="${MAJOR}.${MINOR}.${PATCH}" + TAG="v${NEW}" + + echo "version=${NEW}" >> "$GITHUB_OUTPUT" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "Bumping ${CURRENT} -> ${NEW} (${TAG})" + + - name: Bump versions run: | - TAG="${{ inputs.tag }}" - VERSION="${TAG#v}" - CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - NPM_VERSION=$(node -p 'require("./npm/deskctl/package.json").version') + NEW="${{ steps.version.outputs.version }}" + CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml + node -e 'const fs=require("node:fs"); const p="npm/deskctl/package.json"; const pkg=JSON.parse(fs.readFileSync(p,"utf8")); pkg.version=process.argv[1]; fs.writeFileSync(p, JSON.stringify(pkg, null, 2)+"\n");' "$NEW" + cargo generate-lockfile - test "$VERSION" = "$CARGO_VERSION" - test "$VERSION" = "$NPM_VERSION" + - name: Commit, tag, and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Cargo.toml Cargo.lock npm/deskctl/package.json + git commit -m "release: ${{ steps.version.outputs.tag }} [skip ci]" + git tag "${{ steps.version.outputs.tag }}" + git push origin main --tags - name: Check current published state id: published run: | - VERSION="${{ inputs.tag }}" - VERSION="${VERSION#v}" + VERSION="${{ steps.version.outputs.version }}" if npm view "deskctl@${VERSION}" version >/dev/null 2>&1; then echo "npm=true" >> "$GITHUB_OUTPUT" @@ -102,6 +121,7 @@ jobs: - name: Summary run: | - echo "tag=${{ inputs.tag }}" + echo "tag=${{ steps.version.outputs.tag }}" + echo "bump=${{ inputs.bump }}" echo "npm_already_published=${{ steps.published.outputs.npm }}" echo "crates_already_published=${{ steps.published.outputs.crates }}" From eedb5de2d478acebe6dbd75f17f716ccbb8f0d8c Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:13:10 -0400 Subject: [PATCH 39/64] refresh contributor cache [skip ci] From 2a8b51b4f5249969c6adb2a28ea0ef9238b84667 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:21:43 -0400 Subject: [PATCH 40/64] docs: tighten skill install docs and bundle Co-authored-by: Codex --- README.md | 7 +- site/src/pages/index.astro | 2 +- site/src/pages/installation.mdx | 8 +- skills/deskctl/agents/openai.yaml | 7 ++ skills/deskctl/references/runtime-contract.md | 74 ++++++++++++++++++- 5 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 skills/deskctl/agents/openai.yaml mode change 120000 => 100644 skills/deskctl/references/runtime-contract.md diff --git a/README.md b/README.md index 4b42b5f..f2e746f 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,13 @@ npx deskctl --help ## Installable skill ```bash -npx skills add harivansh-afk/deskctl -s deskctl +npx skills add harivansh-afk/deskctl --skill deskctl -g ``` -The installable skill lives in [`skills/deskctl`](skills/deskctl) and is built around the same observe -> wait -> act -> verify loop as the CLI. +The installable skill lives in [`skills/deskctl`](skills/deskctl), follows the +standard `skills/` repo layout, and installs directly from this GitHub repo via +`npx skills add ...`. It is built around the same observe -> wait -> act -> +verify loop as the CLI. ## Quick example diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 8b8d4b4..e97b599 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -38,7 +38,7 @@ import DocLayout from "../layouts/DocLayout.astro"; There is also an installable skill for `skills.sh`-style agent runtimes:

    -
    npx skills add harivansh-afk/deskctl -s deskctl
    +
    npx skills add harivansh-afk/deskctl --skill deskctl -g

    Links

    diff --git a/site/src/pages/installation.mdx b/site/src/pages/installation.mdx index df53fcc..7754e6b 100644 --- a/site/src/pages/installation.mdx +++ b/site/src/pages/installation.mdx @@ -27,11 +27,13 @@ npx deskctl --help For `skills.sh`-style runtimes: ```sh -npx skills add harivansh-afk/deskctl -s deskctl +npx skills add harivansh-afk/deskctl --skill deskctl -g ``` -The repo skill lives under `skills/deskctl` and is designed around the same -observe -> wait -> act -> verify loop as the CLI. +The repo skill lives under `skills/deskctl`, so `skills` can install it +directly from this GitHub repo. It is designed around the same observe -> wait +-> act -> verify loop as the CLI. `-g` installs it globally; omit that flag if +you want a project-local install. ## Other install paths diff --git a/skills/deskctl/agents/openai.yaml b/skills/deskctl/agents/openai.yaml new file mode 100644 index 0000000..8a5ca13 --- /dev/null +++ b/skills/deskctl/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "deskctl" + short_description: "Control Linux X11 desktops from agent loops" + default_prompt: "Use $deskctl to diagnose the desktop, observe state, wait for UI changes, act deterministically, and verify the result." + +policy: + allow_implicit_invocation: true diff --git a/skills/deskctl/references/runtime-contract.md b/skills/deskctl/references/runtime-contract.md deleted file mode 120000 index 8de0781..0000000 --- a/skills/deskctl/references/runtime-contract.md +++ /dev/null @@ -1 +0,0 @@ -../../../docs/runtime-contract.md \ No newline at end of file diff --git a/skills/deskctl/references/runtime-contract.md b/skills/deskctl/references/runtime-contract.md new file mode 100644 index 0000000..6efd2bc --- /dev/null +++ b/skills/deskctl/references/runtime-contract.md @@ -0,0 +1,73 @@ +# deskctl runtime contract + +This copy ships inside the installable skill so `npx skills add ...` installs a +self-contained reference bundle. + +All commands support `--json` and use the same top-level envelope: + +```json +{ + "success": true, + "data": {}, + "error": null +} +``` + +Use `--json` whenever you need to parse output programmatically. + +## Stable window fields + +Whenever a response includes a window payload, these fields are stable: + +- `ref_id` +- `window_id` +- `title` +- `app_name` +- `x` +- `y` +- `width` +- `height` +- `focused` +- `minimized` + +Use `window_id` for stable targeting inside a live daemon session. Use +`ref_id` or `@wN` for short-lived follow-up actions after `snapshot` or +`list-windows`. + +## Stable grouped reads + +- `deskctl get active-window` -> `data.window` +- `deskctl get monitors` -> `data.count`, `data.monitors` +- `deskctl get version` -> `data.version`, `data.backend` +- `deskctl get systeminfo` -> runtime-scoped diagnostic fields such as + `backend`, `display`, `session_type`, `session`, `socket_path`, `screen`, + `monitor_count`, and `monitors` + +## Stable waits + +- `deskctl wait window` -> `data.wait`, `data.selector`, `data.elapsed_ms`, + `data.window` +- `deskctl wait focus` -> `data.wait`, `data.selector`, `data.elapsed_ms`, + `data.window` + +## Stable structured error kinds + +When a command fails with structured JSON data, these `kind` values are stable: + +- `selector_not_found` +- `selector_ambiguous` +- `selector_invalid` +- `timeout` +- `not_found` + +Wait failures may also include `window_not_focused` in the last observation +payload. + +## Best-effort fields + +Treat these as useful but non-contractual: + +- exact monitor names +- incidental text formatting in non-JSON mode +- default screenshot file names when no explicit path was provided +- environment-dependent ordering details from the window manager From c907e800af804ad44dd844e09f1a0c02d36316a6 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:21:58 -0400 Subject: [PATCH 41/64] change client bin name --- src/cli/mod.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index bab44c9..b24465a 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -7,7 +7,12 @@ use std::path::PathBuf; use crate::core::protocol::{Request, Response}; #[derive(Parser)] -#[command(name = "deskctl", version, about = "Desktop control CLI for AI agents")] +#[command( + name = "deskctl", + bin_name = "deskctl", + version, + about = "Desktop control CLI for AI agents" +)] pub struct App { #[command(flatten)] pub global: GlobalOpts, @@ -988,6 +993,12 @@ mod tests { assert!(help.contains("deskctl snapshot --annotate")); } + #[test] + fn root_help_uses_public_bin_name() { + let help = App::command().render_help().to_string(); + assert!(help.contains("Usage: deskctl [OPTIONS] ")); + } + #[test] fn window_listing_text_includes_window_ids() { let lines = render_success_lines( From 3bfec9eecc890208d6f4f37b97a95534b2a982f5 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:27:43 -0400 Subject: [PATCH 42/64] edit docs --- site/src/pages/index.astro | 13 ------------- site/src/pages/installation.mdx | 15 --------------- 2 files changed, 28 deletions(-) diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index e97b599..b770178 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -21,7 +21,6 @@ import DocLayout from "../layouts/DocLayout.astro";

    Reference

    @@ -34,23 +33,11 @@ import DocLayout from "../layouts/DocLayout.astro";

    Agent skill

    -

    - There is also an installable skill for `skills.sh`-style agent runtimes: -

    - -
    npx skills add harivansh-afk/deskctl --skill deskctl -g
    -

    Links

    diff --git a/site/src/pages/installation.mdx b/site/src/pages/installation.mdx index 7754e6b..ed4e737 100644 --- a/site/src/pages/installation.mdx +++ b/site/src/pages/installation.mdx @@ -10,26 +10,11 @@ toc: true ```sh npm install -g deskctl -deskctl --help ``` `deskctl` is the default install path. It installs the command by downloading the matching GitHub Release asset for the supported runtime target. -## One-shot usage - -```sh -npx deskctl --help -``` - -## Agent skill - -For `skills.sh`-style runtimes: - -```sh -npx skills add harivansh-afk/deskctl --skill deskctl -g -``` - The repo skill lives under `skills/deskctl`, so `skills` can install it directly from this GitHub repo. It is designed around the same observe -> wait -> act -> verify loop as the CLI. `-g` installs it globally; omit that flag if From bf603671f95f28270e4ede426f03442c9203b328 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:28:18 -0400 Subject: [PATCH 43/64] rm: --- site/src/pages/index.astro | 2 -- 1 file changed, 2 deletions(-) diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index b770178..16a4b29 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -31,8 +31,6 @@ import DocLayout from "../layouts/DocLayout.astro";
  • Runtime contract
-

Agent skill

-

Links

    From 848ef97e87c321dffda0a6e4823c3ce8871569e1 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:34:10 -0400 Subject: [PATCH 44/64] edit readme --- README.md | 45 +++++++-------------------------------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index f2e746f..4bc24c8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # deskctl [![npm](https://img.shields.io/npm/v/deskctl?label=npm)](https://www.npmjs.com/package/deskctl) -[![release](https://img.shields.io/github/v/release/harivansh-afk/deskctl?label=release)](https://github.com/harivansh-afk/deskctl/releases) -[![runtime](https://img.shields.io/badge/runtime-linux--x11-111827)](#support-boundary) [![skill](https://img.shields.io/badge/skills.sh-deskctl-111827)](skills/deskctl) -Non-interactive desktop control for AI agents on Linux X11. +Desktop control cli for AI agents on Linux X11. ## Install @@ -15,44 +13,19 @@ deskctl doctor deskctl snapshot --annotate ``` -One-shot execution also works: +## Skill ```bash -npx deskctl --help -``` - -`deskctl` installs the command by downloading the matching GitHub Release asset for the supported runtime target. - - -## Installable skill - -```bash -npx skills add harivansh-afk/deskctl --skill deskctl -g -``` - -The installable skill lives in [`skills/deskctl`](skills/deskctl), follows the -standard `skills/` repo layout, and installs directly from this GitHub repo via -`npx skills add ...`. It is built around the same observe -> wait -> act -> -verify loop as the CLI. - -## Quick example - -```bash -deskctl doctor -deskctl snapshot --annotate -deskctl wait window --selector 'title=Firefox' --timeout 10 -deskctl focus 'title=Firefox' -deskctl type "hello world" +npx skills add harivansh-afk/deskctl ``` ## Docs - runtime contract: [docs/runtime-contract.md](docs/runtime-contract.md) -- release flow: [docs/releasing.md](docs/releasing.md) -- installable skill: [skills/deskctl](skills/deskctl) -- contributor workflow: [CONTRIBUTING.md](CONTRIBUTING.md) +- releasing: [docs/releasing.md](docs/releasing.md) +- contributing: [CONTRIBUTING.md](CONTRIBUTING.md) -## Other install paths +## Install paths Nix: @@ -61,12 +34,8 @@ nix run github:harivansh-afk/deskctl -- --help nix profile install github:harivansh-afk/deskctl ``` -Source build: +Rust: ```bash cargo build ``` - -## Support boundary - -`deskctl` currently supports Linux X11. Use `--json` for stable machine parsing, use `window_id` for programmatic targeting inside a live session, and use `deskctl doctor` first when the runtime looks broken. From 6c6f33040f8be0aec4855c5fd9eef33c6adef4c1 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 09:35:46 -0400 Subject: [PATCH 45/64] update readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4bc24c8..935f329 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ Desktop control cli for AI agents on Linux X11. ```bash npm install -g deskctl +``` + +```bash deskctl doctor deskctl snapshot --annotate ``` From 844f2f2bc6ddb989d1f29bea2725be3741737e53 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Mar 2026 13:37:41 +0000 Subject: [PATCH 46/64] release: v0.1.8 [skip ci] --- Cargo.lock | 2 +- Cargo.toml | 2 +- npm/deskctl/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6922004..3fb1666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deskctl" -version = "0.1.7" +version = "0.1.8" dependencies = [ "ab_glyph", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 5872639..fc7816c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deskctl" -version = "0.1.7" +version = "0.1.8" edition = "2021" description = "X11 desktop control CLI for agents" license = "MIT" diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json index 6085bca..45daefe 100644 --- a/npm/deskctl/package.json +++ b/npm/deskctl/package.json @@ -1,6 +1,6 @@ { "name": "deskctl", - "version": "0.1.7", + "version": "0.1.8", "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", From 2b02513d6ef22ba238e50fea98cc8133c94f2131 Mon Sep 17 00:00:00 2001 From: Hari <73809867+harivansh-afk@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:27:35 -0400 Subject: [PATCH 47/64] Improve docs structure and navigation (#12) * Improve docs structure and navigation Co-authored-by: Codex * rm * handwrite docs --------- Co-authored-by: Codex --- site/src/layouts/DocLayout.astro | 2 +- site/src/pages/architecture.mdx | 98 ----------------------------- site/src/pages/commands.mdx | 24 ++++--- site/src/pages/index.astro | 29 ++++++--- site/src/pages/installation.mdx | 30 ++++++--- site/src/pages/quick-start.mdx | 14 +++-- site/src/pages/runtime-contract.mdx | 4 +- site/src/styles/base.css | 10 +-- 8 files changed, 69 insertions(+), 142 deletions(-) delete mode 100644 site/src/pages/architecture.mdx diff --git a/site/src/layouts/DocLayout.astro b/site/src/layouts/DocLayout.astro index f2608de..afc8648 100644 --- a/site/src/layouts/DocLayout.astro +++ b/site/src/layouts/DocLayout.astro @@ -30,7 +30,7 @@ function formatTocText(text: string): string { { !isIndex && ( -
diff --git a/site/src/pages/installation.mdx b/site/src/pages/installation.mdx index ed4e737..e35f4eb 100644 --- a/site/src/pages/installation.mdx +++ b/site/src/pages/installation.mdx @@ -6,19 +6,30 @@ toc: true # Installation -## Default install +Install the public `deskctl` command first, then validate the desktop runtime +with `deskctl doctor` before trying to automate anything. + +## Recommended path ```sh npm install -g deskctl +deskctl doctor ``` `deskctl` is the default install path. It installs the command by downloading the matching GitHub Release asset for the supported runtime target. -The repo skill lives under `skills/deskctl`, so `skills` can install it -directly from this GitHub repo. It is designed around the same observe -> wait --> act -> verify loop as the CLI. `-g` installs it globally; omit that flag if -you want a project-local install. +This path does not require a Rust toolchain. The installed command is always +`deskctl`, even though the release asset itself is target-specific. + +## Skill install + +The repo skill lives under `skills/deskctl`, so you can install it +directly uring `skills.sh` + +```sh +npx skills add harivansh-afk/deskctl +``` ## Other install paths @@ -29,7 +40,7 @@ nix run github:harivansh-afk/deskctl -- --help nix profile install github:harivansh-afk/deskctl ``` -### Build from source +### Rust ```sh git clone https://github.com/harivansh-afk/deskctl @@ -53,8 +64,13 @@ Source builds on Linux require: The binary itself only depends on the standard Linux glibc runtime. -If setup fails, run: +## Verification + +If setup fails for any reason start here: ```sh deskctl doctor ``` + +`doctor` checks X11 connectivity, window enumeration, screenshot viability, and +daemon/socket health before normal command execution. diff --git a/site/src/pages/quick-start.mdx b/site/src/pages/quick-start.mdx index 10f3ec0..7ecf5a7 100644 --- a/site/src/pages/quick-start.mdx +++ b/site/src/pages/quick-start.mdx @@ -6,17 +6,19 @@ toc: true # Quick start -## Install and diagnose +The fastest way to use `deskctl` is to follow the same four-step loop : observe, wait, act, verify. + +## 1. Install and diagnose ```sh npm install -g deskctl deskctl doctor ``` -Use `deskctl doctor` first. It checks X11 connectivity, basic enumeration, +Run `deskctl doctor` first. It checks X11 connectivity, basic enumeration, screenshot viability, and socket health before you start driving the desktop. -## Observe +## 2. Observe the desktop ```sh deskctl snapshot --annotate @@ -29,7 +31,7 @@ Use `snapshot` when you want a screenshot artifact plus window refs. Use `list-windows` when you only need the current window tree without writing a screenshot. -## Target windows cleanly +## 3. Pick selectors that stay readable Prefer explicit selectors when you need deterministic targeting: @@ -44,7 +46,7 @@ focused Legacy refs such as `@w1` still work after `snapshot` or `list-windows`. Bare strings like `firefox` are fuzzy matches and now fail on ambiguity. -## Wait, act, verify +## 4. Wait, act, verify The core loop is: @@ -69,7 +71,7 @@ deskctl snapshot The wait commands return the matched window payload on success, so they compose cleanly into the next action. -## Use `--json` when parsing matters +## 5. Use `--json` when parsing matters Every command supports `--json` and uses the same top-level envelope: diff --git a/site/src/pages/runtime-contract.mdx b/site/src/pages/runtime-contract.mdx index 4fca14c..e33e999 100644 --- a/site/src/pages/runtime-contract.mdx +++ b/site/src/pages/runtime-contract.mdx @@ -11,7 +11,7 @@ This page defines the current public output contract for `deskctl`. It is intentionally scoped to the current Linux X11 runtime surface. It does not promise stability for future Wayland or window-manager-specific features. -## JSON envelope +## Stable top-level envelope Every command supports `--json` and uses the same top-level envelope: @@ -32,7 +32,7 @@ Stable top-level fields: If `success` is `false`, the command exits non-zero in both text mode and JSON mode. -## Stable window fields +## Stable window payload Whenever a response includes a window payload, these fields are stable: diff --git a/site/src/styles/base.css b/site/src/styles/base.css index cd569a9..e05552e 100644 --- a/site/src/styles/base.css +++ b/site/src/styles/base.css @@ -224,30 +224,30 @@ hr { } } -nav { +.breadcrumbs { max-width: 50rem; margin: 0 auto; padding: 1.5rem clamp(1.25rem, 5vw, 3rem) 0; font-size: 0.9rem; } -nav a { +.breadcrumbs a { color: inherit; text-decoration: none; opacity: 0.6; transition: opacity 0.15s; } -nav a:hover { +.breadcrumbs a:hover { opacity: 1; } -nav .title { +.breadcrumbs .title { font-weight: 500; opacity: 1; } -nav .sep { +.breadcrumbs .sep { opacity: 0.3; margin: 0 0.5em; } From a64b46b479b45310adedf365888fffa458268bf3 Mon Sep 17 00:00:00 2001 From: Hari <73809867+harivansh-afk@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:53:15 -0400 Subject: [PATCH 48/64] deskctl upgrade (#13) * deskctl upgrade * interactive update as well as --yes flag --- npm/deskctl/README.md | 12 + site/src/pages/commands.mdx | 5 +- skills/deskctl/SKILL.md | 6 + skills/deskctl/references/commands.md | 1 + src/cli/mod.rs | 116 ++++++- src/cli/upgrade.rs | 465 ++++++++++++++++++++++++++ 6 files changed, 603 insertions(+), 2 deletions(-) create mode 100644 src/cli/upgrade.rs diff --git a/npm/deskctl/README.md b/npm/deskctl/README.md index 7bb42a9..81f07f4 100644 --- a/npm/deskctl/README.md +++ b/npm/deskctl/README.md @@ -14,6 +14,18 @@ After install, run: deskctl --help ``` +To upgrade version: + +```bash +deskctl upgrade +``` + +For non-interactive use: + +```bash +deskctl upgrade --yes +``` + One-shot usage is also supported: ```bash diff --git a/site/src/pages/commands.mdx b/site/src/pages/commands.mdx index dc9c578..934cdb8 100644 --- a/site/src/pages/commands.mdx +++ b/site/src/pages/commands.mdx @@ -13,6 +13,7 @@ reads, grouped waits, selector-driven actions, and a few input primitives. ```sh deskctl doctor +deskctl upgrade deskctl snapshot deskctl snapshot --annotate deskctl list-windows @@ -26,7 +27,9 @@ deskctl get-screen-size deskctl get-mouse-position ``` -`doctor` checks the runtime before daemon startup. `snapshot` produces a +`doctor` checks the runtime before daemon startup. `upgrade` checks for a newer +published version, shows a short confirmation prompt when an update is +available, and supports `--yes` for non-interactive use. `snapshot` produces a screenshot plus window refs. `list-windows` is the same window tree without the side effect of writing a screenshot. The grouped `get` commands are the preferred read surface for focused state queries. diff --git a/skills/deskctl/SKILL.md b/skills/deskctl/SKILL.md index 244a1fb..67a77c5 100644 --- a/skills/deskctl/SKILL.md +++ b/skills/deskctl/SKILL.md @@ -18,6 +18,12 @@ deskctl doctor deskctl snapshot --annotate ``` +If `deskctl` was installed through npm, refresh it later with: + +```bash +deskctl upgrade --yes +``` + ## Agent loop Every desktop interaction follows: **observe -> wait -> act -> verify**. diff --git a/skills/deskctl/references/commands.md b/skills/deskctl/references/commands.md index 77b9513..27b4310 100644 --- a/skills/deskctl/references/commands.md +++ b/skills/deskctl/references/commands.md @@ -7,6 +7,7 @@ runtime contract. ```bash deskctl doctor +deskctl upgrade deskctl snapshot deskctl snapshot --annotate deskctl list-windows diff --git a/src/cli/mod.rs b/src/cli/mod.rs index b24465a..28092d7 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,4 +1,5 @@ pub mod connection; +pub mod upgrade; use anyhow::Result; use clap::{Args, Parser, Subcommand}; @@ -121,6 +122,9 @@ pub enum Command { /// Diagnose X11 runtime, screenshot, and daemon health #[command(after_help = DOCTOR_EXAMPLES)] Doctor, + /// Upgrade deskctl using the current install channel + #[command(after_help = UPGRADE_EXAMPLES)] + Upgrade(UpgradeOpts), /// Query runtime state #[command(subcommand)] Get(GetCmd), @@ -231,6 +235,8 @@ const GET_SCREEN_SIZE_EXAMPLES: &str = const GET_MOUSE_POSITION_EXAMPLES: &str = "Examples:\n deskctl get-mouse-position\n deskctl --json get-mouse-position"; const DOCTOR_EXAMPLES: &str = "Examples:\n deskctl doctor\n deskctl --json doctor"; +const UPGRADE_EXAMPLES: &str = + "Examples:\n deskctl upgrade\n deskctl upgrade --yes\n deskctl --json upgrade --yes"; const WAIT_WINDOW_EXAMPLES: &str = "Examples:\n deskctl wait window --selector 'title=Firefox' --timeout 10\n deskctl --json wait window --selector 'class=firefox' --poll-ms 100"; const WAIT_FOCUS_EXAMPLES: &str = "Examples:\n deskctl wait focus --selector 'id=win3' --timeout 5\n deskctl wait focus --selector focused --poll-ms 200"; const SCREENSHOT_EXAMPLES: &str = @@ -284,6 +290,13 @@ pub struct WaitSelectorOpts { pub poll_ms: u64, } +#[derive(Args)] +pub struct UpgradeOpts { + /// Skip confirmation and upgrade non-interactively + #[arg(long)] + pub yes: bool, +} + pub fn run() -> Result<()> { let app = App::parse(); @@ -300,6 +313,22 @@ pub fn run() -> Result<()> { return connection::run_doctor(&app.global); } + if let Command::Upgrade(ref upgrade_opts) = app.command { + let response = upgrade::run_upgrade(&app.global, upgrade_opts)?; + let success = response.success; + + if app.global.json { + println!("{}", serde_json::to_string_pretty(&response)?); + if !success { + std::process::exit(1); + } + } else { + print_response(&app.command, &response)?; + } + + return Ok(()); + } + // All other commands need a daemon connection let request = build_request(&app.command)?; let response = connection::send_command(&app.global, &request)?; @@ -363,6 +392,7 @@ fn build_request(cmd: &Command) -> Result { Command::GetScreenSize => Request::new("get-screen-size"), Command::GetMousePosition => Request::new("get-mouse-position"), Command::Doctor => unreachable!(), + Command::Upgrade(_) => unreachable!(), Command::Get(sub) => match sub { GetCmd::ActiveWindow => Request::new("get-active-window"), GetCmd::Monitors => Request::new("get-monitors"), @@ -422,6 +452,7 @@ fn render_success_lines(cmd: &Command, data: Option<&serde_json::Value>) -> Resu Command::Get(GetCmd::Systeminfo) => render_systeminfo_lines(data), Command::GetScreenSize => vec![render_screen_size_line(data)], Command::GetMousePosition => vec![render_mouse_position_line(data)], + Command::Upgrade(_) => render_upgrade_lines(data), Command::Screenshot { annotate, .. } => render_screenshot_lines(data, *annotate), Command::Click { .. } => vec![render_click_line(data, false)], Command::Dblclick { .. } => vec![render_click_line(data, true)], @@ -526,6 +557,41 @@ fn render_error_lines(response: &Response) -> Vec { lines.push("No focused window is available.".to_string()); } } + "upgrade_failed" => { + if let Some(reason) = data.get("io_error").and_then(|value| value.as_str()) { + lines.push(format!("Reason: {reason}")); + } + if let Some(reason) = data.get("reason").and_then(|value| value.as_str()) { + lines.push(format!("Reason: {reason}")); + } + if let Some(command) = data.get("command").and_then(|value| value.as_str()) { + lines.push(format!("Command: {command}")); + } + if let Some(hint) = data.get("hint").and_then(|value| value.as_str()) { + lines.push(format!("Hint: {hint}")); + } + } + "upgrade_unsupported" => { + if let Some(hint) = data.get("hint").and_then(|value| value.as_str()) { + lines.push(format!("Hint: {hint}")); + } + } + "upgrade_confirmation_required" => { + if let Some(current_version) = + data.get("current_version").and_then(|value| value.as_str()) + { + if let Some(latest_version) = + data.get("latest_version").and_then(|value| value.as_str()) + { + lines.push(format!( + "Update available: {current_version} -> {latest_version}" + )); + } + } + if let Some(hint) = data.get("hint").and_then(|value| value.as_str()) { + lines.push(format!("Hint: {hint}")); + } + } _ => {} } @@ -723,6 +789,36 @@ fn render_screenshot_lines(data: &serde_json::Value, annotate: bool) -> Vec Vec { + match data.get("status").and_then(|value| value.as_str()) { + Some("up_to_date") => { + let version = data + .get("latest_version") + .and_then(|value| value.as_str()) + .or_else(|| data.get("current_version").and_then(|value| value.as_str())) + .unwrap_or("unknown"); + vec![format!( + "✔ You're already on the latest version! ({version})" + )] + } + Some("upgraded") => { + let current_version = data + .get("current_version") + .and_then(|value| value.as_str()) + .unwrap_or("unknown"); + let latest_version = data + .get("latest_version") + .and_then(|value| value.as_str()) + .unwrap_or("unknown"); + vec![format!( + "✔ Upgraded deskctl from {current_version} -> {latest_version}" + )] + } + Some("cancelled") => vec!["No changes made.".to_string()], + _ => vec!["Upgrade completed.".to_string()], + } +} + fn render_click_line(data: &serde_json::Value, double: bool) -> String { let action = if double { "Double-clicked" } else { "Clicked" }; let key = if double { "double_clicked" } else { "clicked" }; @@ -978,7 +1074,7 @@ fn truncate_display(value: &str, max_chars: usize) -> String { mod tests { use super::{ render_error_lines, render_screen_size_line, render_success_lines, target_summary, - truncate_display, App, Command, Response, + truncate_display, App, Command, Response, UpgradeOpts, }; use clap::CommandFactory; use serde_json::json; @@ -1104,4 +1200,22 @@ mod tests { let input = format!("fire{}fox", '\u{00E9}'); assert_eq!(truncate_display(&input, 7), "fire..."); } + + #[test] + fn upgrade_success_text_is_neat() { + let lines = render_success_lines( + &Command::Upgrade(UpgradeOpts { yes: false }), + Some(&json!({ + "status": "up_to_date", + "current_version": "0.1.8", + "latest_version": "0.1.8" + })), + ) + .unwrap(); + + assert_eq!( + lines, + vec!["✔ You're already on the latest version! (0.1.8)"] + ); + } } diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs new file mode 100644 index 0000000..acc844e --- /dev/null +++ b/src/cli/upgrade.rs @@ -0,0 +1,465 @@ +use std::io::{self, IsTerminal, Write}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use anyhow::{Context, Result}; +use serde_json::json; + +use crate::cli::{GlobalOpts, UpgradeOpts}; +use crate::core::protocol::Response; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum InstallMethod { + Npm, + Cargo, + Nix, + Source, + Unknown, +} + +impl InstallMethod { + fn as_str(self) -> &'static str { + match self { + Self::Npm => "npm", + Self::Cargo => "cargo", + Self::Nix => "nix", + Self::Source => "source", + Self::Unknown => "unknown", + } + } +} + +#[derive(Debug)] +struct UpgradePlan { + install_method: InstallMethod, + program: &'static str, + args: Vec<&'static str>, +} + +impl UpgradePlan { + fn command_line(&self) -> String { + std::iter::once(self.program) + .chain(self.args.iter().copied()) + .collect::>() + .join(" ") + } +} + +#[derive(Debug)] +struct VersionInfo { + current: String, + latest: String, +} + +pub fn run_upgrade(opts: &GlobalOpts, upgrade_opts: &UpgradeOpts) -> Result { + let current_exe = std::env::current_exe().context("Failed to determine executable path")?; + let install_method = detect_install_method(¤t_exe); + + let Some(plan) = upgrade_plan(install_method) else { + return Ok(Response::err_with_data( + format!( + "deskctl upgrade is not supported for {} installs.", + install_method.as_str() + ), + json!({ + "kind": "upgrade_unsupported", + "install_method": install_method.as_str(), + "current_exe": current_exe.display().to_string(), + "hint": upgrade_hint(install_method), + }), + )); + }; + + if !opts.json { + println!("- Checking for updates..."); + } + + let versions = match resolve_versions(&plan) { + Ok(versions) => versions, + Err(response) => return Ok(response), + }; + + if versions.current == versions.latest { + return Ok(Response::ok(json!({ + "action": "upgrade", + "status": "up_to_date", + "install_method": plan.install_method.as_str(), + "current_version": versions.current, + "latest_version": versions.latest, + }))); + } + + if !upgrade_opts.yes { + if opts.json || !io::stdin().is_terminal() { + return Ok(Response::err_with_data( + format!( + "Upgrade confirmation required for {} -> {}.", + versions.current, versions.latest + ), + json!({ + "kind": "upgrade_confirmation_required", + "install_method": plan.install_method.as_str(), + "current_version": versions.current, + "latest_version": versions.latest, + "command": plan.command_line(), + "hint": "Re-run with --yes to upgrade non-interactively.", + }), + )); + } + + if !confirm_upgrade(&versions)? { + return Ok(Response::ok(json!({ + "action": "upgrade", + "status": "cancelled", + "install_method": plan.install_method.as_str(), + "current_version": versions.current, + "latest_version": versions.latest, + }))); + } + } + + if !opts.json { + println!( + "- Upgrading deskctl from {} -> {}...", + versions.current, versions.latest + ); + } + + let output = match Command::new(plan.program).args(&plan.args).output() { + Ok(output) => output, + Err(error) => return Ok(upgrade_spawn_error_response(&plan, &versions, &error)), + }; + + if output.status.success() { + return Ok(Response::ok(json!({ + "action": "upgrade", + "status": "upgraded", + "install_method": plan.install_method.as_str(), + "current_version": versions.current, + "latest_version": versions.latest, + "command": plan.command_line(), + "exit_code": output.status.code(), + }))); + } + + Ok(upgrade_command_failed_response(&plan, &versions, &output)) +} + +fn resolve_versions(plan: &UpgradePlan) -> std::result::Result { + let current = env!("CARGO_PKG_VERSION").to_string(); + let latest = match plan.install_method { + InstallMethod::Npm => query_npm_latest_version()?, + InstallMethod::Cargo => query_cargo_latest_version()?, + InstallMethod::Nix | InstallMethod::Source | InstallMethod::Unknown => { + return Err(Response::err_with_data( + "Could not determine the latest published version.".to_string(), + json!({ + "kind": "upgrade_failed", + "install_method": plan.install_method.as_str(), + "reason": "Could not determine the latest published version for this install method.", + "command": plan.command_line(), + "hint": upgrade_hint(plan.install_method), + }), + )); + } + }; + + Ok(VersionInfo { current, latest }) +} + +fn query_npm_latest_version() -> std::result::Result { + let output = Command::new("npm") + .args(["view", "deskctl", "version", "--json"]) + .output() + .map_err(|error| { + Response::err_with_data( + "Failed to check the latest npm version.".to_string(), + json!({ + "kind": "upgrade_failed", + "install_method": InstallMethod::Npm.as_str(), + "reason": "Failed to run npm view deskctl version --json.", + "io_error": error.to_string(), + "command": "npm view deskctl version --json", + "hint": upgrade_hint(InstallMethod::Npm), + }), + ) + })?; + + if !output.status.success() { + return Err(Response::err_with_data( + "Failed to check the latest npm version.".to_string(), + json!({ + "kind": "upgrade_failed", + "install_method": InstallMethod::Npm.as_str(), + "reason": command_failure_reason(&output), + "command": "npm view deskctl version --json", + "hint": upgrade_hint(InstallMethod::Npm), + }), + )); + } + + serde_json::from_slice::(&output.stdout).map_err(|_| { + Response::err_with_data( + "Failed to parse the latest npm version.".to_string(), + json!({ + "kind": "upgrade_failed", + "install_method": InstallMethod::Npm.as_str(), + "reason": "npm view returned an unexpected version payload.", + "command": "npm view deskctl version --json", + "hint": upgrade_hint(InstallMethod::Npm), + }), + ) + }) +} + +fn query_cargo_latest_version() -> std::result::Result { + let output = Command::new("cargo") + .args(["search", "deskctl", "--limit", "1"]) + .output() + .map_err(|error| { + Response::err_with_data( + "Failed to check the latest crates.io version.".to_string(), + json!({ + "kind": "upgrade_failed", + "install_method": InstallMethod::Cargo.as_str(), + "reason": "Failed to run cargo search deskctl --limit 1.", + "io_error": error.to_string(), + "command": "cargo search deskctl --limit 1", + "hint": upgrade_hint(InstallMethod::Cargo), + }), + ) + })?; + + if !output.status.success() { + return Err(Response::err_with_data( + "Failed to check the latest crates.io version.".to_string(), + json!({ + "kind": "upgrade_failed", + "install_method": InstallMethod::Cargo.as_str(), + "reason": command_failure_reason(&output), + "command": "cargo search deskctl --limit 1", + "hint": upgrade_hint(InstallMethod::Cargo), + }), + )); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let latest = stdout + .split('"') + .nth(1) + .map(str::to_string) + .filter(|value| !value.is_empty()); + + latest.ok_or_else(|| { + Response::err_with_data( + "Failed to determine the latest crates.io version.".to_string(), + json!({ + "kind": "upgrade_failed", + "install_method": InstallMethod::Cargo.as_str(), + "reason": "cargo search did not return a published deskctl crate version.", + "command": "cargo search deskctl --limit 1", + "hint": upgrade_hint(InstallMethod::Cargo), + }), + ) + }) +} + +fn confirm_upgrade(versions: &VersionInfo) -> Result { + print!( + "Upgrade deskctl from {} -> {}? [y/N] ", + versions.current, versions.latest + ); + io::stdout().flush()?; + + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + + let trimmed = input.trim(); + Ok(matches!(trimmed, "y" | "Y" | "yes" | "YES" | "Yes")) +} + +fn upgrade_command_failed_response( + plan: &UpgradePlan, + versions: &VersionInfo, + output: &std::process::Output, +) -> Response { + Response::err_with_data( + format!("Upgrade command failed: {}", plan.command_line()), + json!({ + "kind": "upgrade_failed", + "install_method": plan.install_method.as_str(), + "current_version": versions.current, + "latest_version": versions.latest, + "command": plan.command_line(), + "exit_code": output.status.code(), + "reason": command_failure_reason(output), + "hint": upgrade_hint(plan.install_method), + }), + ) +} + +fn upgrade_spawn_error_response( + plan: &UpgradePlan, + versions: &VersionInfo, + error: &std::io::Error, +) -> Response { + Response::err_with_data( + format!("Failed to run {}", plan.command_line()), + json!({ + "kind": "upgrade_failed", + "install_method": plan.install_method.as_str(), + "current_version": versions.current, + "latest_version": versions.latest, + "command": plan.command_line(), + "io_error": error.to_string(), + "hint": upgrade_hint(plan.install_method), + }), + ) +} + +fn command_failure_reason(output: &std::process::Output) -> String { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + + stderr + .lines() + .chain(stdout.lines()) + .map(str::trim) + .find(|line| !line.is_empty()) + .map(str::to_string) + .unwrap_or_else(|| { + output + .status + .code() + .map(|code| format!("Command exited with status {code}.")) + .unwrap_or_else(|| "Command exited unsuccessfully.".to_string()) + }) +} + +fn upgrade_plan(install_method: InstallMethod) -> Option { + match install_method { + InstallMethod::Npm => Some(UpgradePlan { + install_method, + program: "npm", + args: vec!["install", "-g", "deskctl@latest"], + }), + InstallMethod::Cargo => Some(UpgradePlan { + install_method, + program: "cargo", + args: vec!["install", "deskctl", "--locked"], + }), + InstallMethod::Nix | InstallMethod::Source | InstallMethod::Unknown => None, + } +} + +fn upgrade_hint(install_method: InstallMethod) -> &'static str { + match install_method { + InstallMethod::Nix => { + "Use nix profile upgrade or update the flake reference you installed from." + } + InstallMethod::Source => { + "Rebuild from source or reinstall deskctl through npm, cargo, or nix." + } + InstallMethod::Unknown => { + "Reinstall deskctl through a supported channel such as npm, cargo, or nix." + } + InstallMethod::Npm => "Retry with --yes or run npm install -g deskctl@latest directly.", + InstallMethod::Cargo => "Retry with --yes or run cargo install deskctl --locked directly.", + } +} + +fn detect_install_method(current_exe: &Path) -> InstallMethod { + if looks_like_npm_install(current_exe) { + return InstallMethod::Npm; + } + if looks_like_nix_install(current_exe) { + return InstallMethod::Nix; + } + if looks_like_cargo_install(current_exe) { + return InstallMethod::Cargo; + } + if looks_like_source_tree(current_exe) { + return InstallMethod::Source; + } + InstallMethod::Unknown +} + +fn looks_like_npm_install(path: &Path) -> bool { + let value = normalize(path); + value.contains("/node_modules/deskctl/") && value.contains("/vendor/") +} + +fn looks_like_nix_install(path: &Path) -> bool { + normalize(path).starts_with("/nix/store/") +} + +fn looks_like_cargo_install(path: &Path) -> bool { + let Some(home) = std::env::var_os("HOME") else { + return false; + }; + + let cargo_home = std::env::var_os("CARGO_HOME") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from(home).join(".cargo")); + path == cargo_home.join("bin").join("deskctl") +} + +fn looks_like_source_tree(path: &Path) -> bool { + let value = normalize(path); + value.contains("/target/debug/deskctl") || value.contains("/target/release/deskctl") +} + +fn normalize(path: &Path) -> String { + path.to_string_lossy().replace('\\', "/") +} + +#[cfg(test)] +mod tests { + use std::os::unix::process::ExitStatusExt; + use std::path::Path; + + use super::{command_failure_reason, detect_install_method, upgrade_plan, InstallMethod}; + + #[test] + fn detects_npm_install_path() { + let method = detect_install_method(Path::new( + "/usr/local/lib/node_modules/deskctl/vendor/deskctl-linux-x86_64", + )); + assert_eq!(method, InstallMethod::Npm); + } + + #[test] + fn detects_nix_install_path() { + let method = detect_install_method(Path::new("/nix/store/abc123-deskctl/bin/deskctl")); + assert_eq!(method, InstallMethod::Nix); + } + + #[test] + fn detects_source_tree_path() { + let method = + detect_install_method(Path::new("/Users/example/src/deskctl/target/debug/deskctl")); + assert_eq!(method, InstallMethod::Source); + } + + #[test] + fn npm_upgrade_plan_uses_global_install() { + let plan = upgrade_plan(InstallMethod::Npm).expect("npm installs should support upgrade"); + assert_eq!(plan.command_line(), "npm install -g deskctl@latest"); + } + + #[test] + fn nix_install_has_no_upgrade_plan() { + assert!(upgrade_plan(InstallMethod::Nix).is_none()); + } + + #[test] + fn failure_reason_prefers_stderr() { + let output = std::process::Output { + status: std::process::ExitStatus::from_raw(1 << 8), + stdout: b"".to_vec(), + stderr: b"boom\n".to_vec(), + }; + + assert_eq!(command_failure_reason(&output), "boom"); + } +} From e61c5bc33f7f51d8b43703cfc8e0c068f751e57a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Mar 2026 16:03:29 +0000 Subject: [PATCH 49/64] release: v0.1.9 [skip ci] --- Cargo.lock | 2 +- Cargo.toml | 2 +- npm/deskctl/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fb1666..157dbc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deskctl" -version = "0.1.8" +version = "0.1.9" dependencies = [ "ab_glyph", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index fc7816c..2ebe138 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deskctl" -version = "0.1.8" +version = "0.1.9" edition = "2021" description = "X11 desktop control CLI for agents" license = "MIT" diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json index 45daefe..5dfeaa0 100644 --- a/npm/deskctl/package.json +++ b/npm/deskctl/package.json @@ -1,6 +1,6 @@ { "name": "deskctl", - "version": "0.1.8", + "version": "0.1.9", "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", From 07a478b0eed0e5df22cb5a1bd16989c3b8f57d33 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 14:21:41 -0400 Subject: [PATCH 50/64] couple CI with publish --- .github/workflows/ci.yml | 143 ++++++++++++++++++++++++++++++++-- .github/workflows/publish.yml | 127 ------------------------------ 2 files changed, 137 insertions(+), 133 deletions(-) delete mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb36e61..bcb02b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,23 @@ on: push: branches: [main] workflow_dispatch: + inputs: + bump: + description: Version bump type (only for workflow_dispatch) + type: choice + options: + - patch + - minor + - major + default: patch + publish_npm: + description: Publish to npm + type: boolean + default: true + publish_crates: + description: Publish to crates.io + type: boolean + default: false permissions: contents: write @@ -52,13 +69,34 @@ jobs: echo "rust=${{ steps.filter.outputs.rust }}" >> "$GITHUB_OUTPUT" fi - - name: Read current version + - name: Calculate next version id: version if: github.event_name != 'pull_request' && steps.check.outputs.rust == 'true' run: | - VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" + CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" + + BUMP="${{ inputs.bump || 'patch' }}" + case "$BUMP" in + major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; + minor) MINOR=$((MINOR + 1)); PATCH=0 ;; + patch) + LATEST=$(git tag -l "v${MAJOR}.${MINOR}.*" | sort -V | tail -1) + if [ -z "$LATEST" ]; then + NEW_PATCH=$PATCH + else + LATEST_VER="${LATEST#v}" + IFS='.' read -r _ _ LATEST_PATCH <<< "$LATEST_VER" + NEW_PATCH=$((LATEST_PATCH + 1)) + fi + PATCH=$NEW_PATCH + ;; + esac + + NEW="${MAJOR}.${MINOR}.${PATCH}" + echo "version=${NEW}" >> "$GITHUB_OUTPUT" + echo "tag=v${NEW}" >> "$GITHUB_OUTPUT" + echo "Computed version: ${NEW} (v${NEW})" validate: name: Validate @@ -177,10 +215,53 @@ jobs: path: target/release/deskctl retention-days: 7 + update-manifests: + name: Update Manifests + needs: [changes, build] + if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: dtolnay/rust-toolchain@stable + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Update versions + run: | + CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + NEW="${{ needs.changes.outputs.version }}" + if [ "$CURRENT" != "$NEW" ]; then + sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml + cargo generate-lockfile + fi + node -e ' + const fs = require("node:fs"); + const p = "npm/deskctl/package.json"; + const pkg = JSON.parse(fs.readFileSync(p, "utf8")); + pkg.version = process.argv[1]; + fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + "\n"); + ' "$NEW" + + - name: Commit, tag, and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Cargo.toml Cargo.lock npm/deskctl/package.json + if ! git diff --cached --quiet; then + git commit -m "release: ${{ needs.changes.outputs.tag }} [skip ci]" + fi + git tag "${{ needs.changes.outputs.tag }}" + git push origin main --tags + release: name: Release - needs: [changes, build] - if: github.event_name != 'pull_request' + needs: [changes, build, update-manifests] + if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -209,3 +290,53 @@ jobs: artifacts/deskctl-linux-x86_64 \ artifacts/checksums.txt fi + + publish: + name: Publish + needs: [changes, update-manifests, release] + if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.changes.outputs.tag }} + + - uses: dtolnay/rust-toolchain@stable + + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev + + - name: Check current published state + id: published + run: | + VERSION="${{ needs.changes.outputs.version }}" + if npm view "deskctl@${VERSION}" version >/dev/null 2>&1; then + echo "npm=true" >> "$GITHUB_OUTPUT" + else + echo "npm=false" >> "$GITHUB_OUTPUT" + fi + if curl -fsSL "https://crates.io/api/v1/crates/deskctl/${VERSION}" >/dev/null 2>&1; then + echo "crates=true" >> "$GITHUB_OUTPUT" + else + echo "crates=false" >> "$GITHUB_OUTPUT" + fi + + - name: Validate npm package + run: node npm/deskctl/scripts/validate-package.js + + - name: Publish npm + if: steps.published.outputs.npm != 'true' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish ./npm/deskctl --access public + + - name: Publish crates.io + if: inputs.publish_crates && steps.published.outputs.crates != 'true' + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --locked diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 60aed4d..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Publish Registries - -on: - workflow_dispatch: - inputs: - bump: - description: Version bump type - required: true - type: choice - options: - - patch - - minor - - major - publish_npm: - description: Publish deskctl to npm - required: true - type: boolean - default: true - publish_crates: - description: Publish deskctl to crates.io - required: true - type: boolean - default: false - -permissions: - contents: read - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: dtolnay/rust-toolchain@stable - - - uses: actions/setup-node@v4 - with: - node-version: 22 - registry-url: https://registry.npmjs.org - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev - - - name: Compute next version - id: version - run: | - CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" - - case "${{ inputs.bump }}" in - major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; - minor) MINOR=$((MINOR + 1)); PATCH=0 ;; - patch) PATCH=$((PATCH + 1)) ;; - esac - - NEW="${MAJOR}.${MINOR}.${PATCH}" - TAG="v${NEW}" - - echo "version=${NEW}" >> "$GITHUB_OUTPUT" - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - echo "Bumping ${CURRENT} -> ${NEW} (${TAG})" - - - name: Bump versions - run: | - NEW="${{ steps.version.outputs.version }}" - CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml - node -e 'const fs=require("node:fs"); const p="npm/deskctl/package.json"; const pkg=JSON.parse(fs.readFileSync(p,"utf8")); pkg.version=process.argv[1]; fs.writeFileSync(p, JSON.stringify(pkg, null, 2)+"\n");' "$NEW" - cargo generate-lockfile - - - name: Commit, tag, and push - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add Cargo.toml Cargo.lock npm/deskctl/package.json - git commit -m "release: ${{ steps.version.outputs.tag }} [skip ci]" - git tag "${{ steps.version.outputs.tag }}" - git push origin main --tags - - - name: Check current published state - id: published - run: | - VERSION="${{ steps.version.outputs.version }}" - - if npm view "deskctl@${VERSION}" version >/dev/null 2>&1; then - echo "npm=true" >> "$GITHUB_OUTPUT" - else - echo "npm=false" >> "$GITHUB_OUTPUT" - fi - - if curl -fsSL "https://crates.io/api/v1/crates/deskctl/${VERSION}" >/dev/null 2>&1; then - echo "crates=true" >> "$GITHUB_OUTPUT" - else - echo "crates=false" >> "$GITHUB_OUTPUT" - fi - - - name: Validate npm package - run: | - mkdir -p ./tmp/npm-pack - node npm/deskctl/scripts/validate-package.js - npm pack ./npm/deskctl --pack-destination ./tmp/npm-pack >/dev/null - - - name: Validate crate publish path - run: cargo publish --dry-run --locked - - - name: Publish npm - if: inputs.publish_npm && steps.published.outputs.npm != 'true' - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: npm publish ./npm/deskctl --access public - - - name: Publish crates.io - if: inputs.publish_crates && steps.published.outputs.crates != 'true' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish --locked - - - name: Summary - run: | - echo "tag=${{ steps.version.outputs.tag }}" - echo "bump=${{ inputs.bump }}" - echo "npm_already_published=${{ steps.published.outputs.npm }}" - echo "crates_already_published=${{ steps.published.outputs.crates }}" From 8d690a62b43a54d41f5b49c07f025bb4d419e3e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Mar 2026 18:28:13 +0000 Subject: [PATCH 51/64] release: v0.1.10 [skip ci] --- Cargo.lock | 2 +- Cargo.toml | 2 +- npm/deskctl/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 157dbc7..9680966 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deskctl" -version = "0.1.9" +version = "0.1.10" dependencies = [ "ab_glyph", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 2ebe138..cc6d11a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deskctl" -version = "0.1.9" +version = "0.1.10" edition = "2021" description = "X11 desktop control CLI for agents" license = "MIT" diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json index 5dfeaa0..adb142c 100644 --- a/npm/deskctl/package.json +++ b/npm/deskctl/package.json @@ -1,6 +1,6 @@ { "name": "deskctl", - "version": "0.1.9", + "version": "0.1.10", "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", From a58912284b2a797c0d422182aa16c52ade05e580 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 26 Mar 2026 14:40:45 -0400 Subject: [PATCH 52/64] reorder pipeline --- .github/workflows/ci.yml | 76 +++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcb02b3..dcef6fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,9 @@ on: type: boolean default: false +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + permissions: contents: write @@ -186,38 +189,12 @@ jobs: - name: Distribution validation run: make dist-validate - build: - name: Build Release Asset - needs: [changes, validate, integration, distribution] - if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - - uses: Swatinem/rust-cache@v2 - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev - - - name: Clippy - run: cargo clippy -- -D warnings - - - name: Build - run: cargo build --release --locked - - - uses: actions/upload-artifact@v4 - with: - name: deskctl-linux-x86_64 - path: target/release/deskctl - retention-days: 7 + # --- Release pipeline: update-manifests -> build -> release -> publish --- + # Version bump happens BEFORE build so the binary has the correct version. update-manifests: name: Update Manifests - needs: [changes, build] + needs: [changes, validate, integration, distribution] if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true' runs-on: ubuntu-latest steps: @@ -258,6 +235,47 @@ jobs: git tag "${{ needs.changes.outputs.tag }}" git push origin main --tags + build: + name: Build Release Asset + needs: [changes, update-manifests] + if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.changes.outputs.tag }} + + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - uses: Swatinem/rust-cache@v2 + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev + + - name: Verify version + run: | + CARGO_VER=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + EXPECTED="${{ needs.changes.outputs.version }}" + if [ "$CARGO_VER" != "$EXPECTED" ]; then + echo "Version mismatch: Cargo.toml=$CARGO_VER expected=$EXPECTED" + exit 1 + fi + echo "Building version $CARGO_VER" + + - name: Clippy + run: cargo clippy -- -D warnings + + - name: Build + run: cargo build --release --locked + + - uses: actions/upload-artifact@v4 + with: + name: deskctl-linux-x86_64 + path: target/release/deskctl + retention-days: 7 + release: name: Release needs: [changes, build, update-manifests] From 580ea79c276fb5eafb35300415441263bb523d04 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Mar 2026 18:47:09 +0000 Subject: [PATCH 53/64] release: v0.1.11 [skip ci] --- Cargo.lock | 2 +- Cargo.toml | 2 +- npm/deskctl/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9680966..b411e80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deskctl" -version = "0.1.10" +version = "0.1.11" dependencies = [ "ab_glyph", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index cc6d11a..59108df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deskctl" -version = "0.1.10" +version = "0.1.11" edition = "2021" description = "X11 desktop control CLI for agents" license = "MIT" diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json index adb142c..60b8ed4 100644 --- a/npm/deskctl/package.json +++ b/npm/deskctl/package.json @@ -1,6 +1,6 @@ { "name": "deskctl", - "version": "0.1.10", + "version": "0.1.11", "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", From ff26c570351fc9d3690bf20958291da5f0bf0e89 Mon Sep 17 00:00:00 2001 From: Hari <73809867+harivansh-afk@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:25:40 -0400 Subject: [PATCH 54/64] replace firefox with chrome (#14) --- site/src/pages/commands.mdx | 14 +++--- site/src/pages/quick-start.mdx | 16 +++---- skills/deskctl/SKILL.md | 10 ++--- skills/deskctl/references/commands.md | 12 ++--- skills/deskctl/workflows/observe-act.sh | 2 +- src/cli/mod.rs | 58 ++++++++++++------------- src/core/refs.rs | 12 ++--- 7 files changed, 61 insertions(+), 63 deletions(-) diff --git a/site/src/pages/commands.mdx b/site/src/pages/commands.mdx index 934cdb8..0696558 100644 --- a/site/src/pages/commands.mdx +++ b/site/src/pages/commands.mdx @@ -37,9 +37,9 @@ preferred read surface for focused state queries. ## Wait for state transitions ```sh -deskctl wait window --selector 'title=Firefox' --timeout 10 +deskctl wait window --selector 'title=Chromium' --timeout 10 deskctl wait focus --selector 'id=win3' --timeout 5 -deskctl --json wait window --selector 'class=firefox' --poll-ms 100 +deskctl --json wait window --selector 'class=chromium' --poll-ms 100 ``` Wait commands return the matched window payload on success. In `--json` mode, @@ -48,9 +48,9 @@ timeouts and selector failures expose structured `kind` values. ## Act on windows ```sh -deskctl launch firefox +deskctl launch chromium deskctl focus @w1 -deskctl focus 'title=Firefox' +deskctl focus 'title=Chromium' deskctl click @w1 deskctl click 960,540 deskctl dblclick @w2 @@ -86,8 +86,8 @@ more deterministic for automation, and easier to retry safely. ```sh ref=w1 id=win1 -title=Firefox -class=firefox +title=Chromium +class=chromium focused ``` @@ -99,7 +99,7 @@ w1 win1 ``` -Bare strings like `firefox` are fuzzy matches. They resolve when there is one +Bare strings like `chromium` are fuzzy matches. They resolve when there is one match and fail with candidate windows when there are multiple matches. ## Global options diff --git a/site/src/pages/quick-start.mdx b/site/src/pages/quick-start.mdx index 7ecf5a7..4cc0e25 100644 --- a/site/src/pages/quick-start.mdx +++ b/site/src/pages/quick-start.mdx @@ -38,13 +38,13 @@ Prefer explicit selectors when you need deterministic targeting: ```sh ref=w1 id=win1 -title=Firefox -class=firefox +title=Chromium +class=chromium focused ``` Legacy refs such as `@w1` still work after `snapshot` or `list-windows`. Bare -strings like `firefox` are fuzzy matches and now fail on ambiguity. +strings like `chromium` are fuzzy matches and now fail on ambiguity. ## 4. Wait, act, verify @@ -55,16 +55,16 @@ The core loop is: deskctl snapshot --annotate # wait -deskctl wait window --selector 'title=Firefox' --timeout 10 +deskctl wait window --selector 'title=Chromium' --timeout 10 # act -deskctl focus 'title=Firefox' +deskctl focus 'title=Chromium' deskctl hotkey ctrl l deskctl type "https://example.com" deskctl press enter # verify -deskctl wait focus --selector 'title=Firefox' --timeout 5 +deskctl wait focus --selector 'title=Chromium' --timeout 5 deskctl snapshot ``` @@ -84,8 +84,8 @@ Every command supports `--json` and uses the same top-level envelope: { "ref_id": "w1", "window_id": "win1", - "title": "Firefox", - "app_name": "firefox", + "title": "Chromium", + "app_name": "chromium", "x": 0, "y": 0, "width": 1920, diff --git a/skills/deskctl/SKILL.md b/skills/deskctl/SKILL.md index 67a77c5..c79ca21 100644 --- a/skills/deskctl/SKILL.md +++ b/skills/deskctl/SKILL.md @@ -30,8 +30,8 @@ Every desktop interaction follows: **observe -> wait -> act -> verify**. ```bash deskctl snapshot --annotate # observe -deskctl wait window --selector 'title=Firefox' --timeout 10 # wait -deskctl click 'title=Firefox' # act +deskctl wait window --selector 'title=Chromium' --timeout 10 # wait +deskctl click 'title=Chromium' # act deskctl snapshot # verify ``` @@ -42,12 +42,12 @@ See [workflows/observe-act.sh](workflows/observe-act.sh) for a reusable script. ```bash ref=w1 # snapshot ref (short-lived) id=win1 # stable window ID (session-scoped) -title=Firefox # match by title -class=firefox # match by WM class +title=Chromium # match by title +class=chromium # match by WM class focused # currently focused window ``` -Bare strings like `firefox` do fuzzy matching but fail on ambiguity. Prefer explicit selectors. +Bare strings like `chromium` do fuzzy matching but fail on ambiguity. Prefer explicit selectors. ## References diff --git a/skills/deskctl/references/commands.md b/skills/deskctl/references/commands.md index 27b4310..df69350 100644 --- a/skills/deskctl/references/commands.md +++ b/skills/deskctl/references/commands.md @@ -23,8 +23,8 @@ deskctl get-mouse-position ## Wait ```bash -deskctl wait window --selector 'title=Firefox' --timeout 10 -deskctl wait focus --selector 'class=firefox' --timeout 5 +deskctl wait window --selector 'title=Chromium' --timeout 10 +deskctl wait focus --selector 'class=chromium' --timeout 5 ``` Returns the matched window payload on success. Failures include structured @@ -35,8 +35,8 @@ Returns the matched window payload on success. Failures include structured ```bash ref=w1 id=win1 -title=Firefox -class=firefox +title=Chromium +class=chromium focused ``` @@ -46,7 +46,7 @@ on ambiguity. ## Act ```bash -deskctl focus 'class=firefox' +deskctl focus 'class=chromium' deskctl click @w1 deskctl dblclick @w2 deskctl type "hello world" @@ -59,7 +59,7 @@ deskctl mouse drag 100 100 500 500 deskctl move-window @w1 100 120 deskctl resize-window @w1 1280 720 deskctl close @w3 -deskctl launch firefox +deskctl launch chromium ``` The daemon starts automatically on first command. In normal usage you should diff --git a/skills/deskctl/workflows/observe-act.sh b/skills/deskctl/workflows/observe-act.sh index 0e336ae..8c3abc2 100755 --- a/skills/deskctl/workflows/observe-act.sh +++ b/skills/deskctl/workflows/observe-act.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # observe-act.sh - main desktop interaction loop # usage: ./observe-act.sh [action] [action-args...] -# example: ./observe-act.sh 'title=Firefox' click +# example: ./observe-act.sh 'title=Chromium' click # example: ./observe-act.sh 'class=terminal' type "ls -la" set -euo pipefail diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 28092d7..79008de 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -48,13 +48,13 @@ pub enum Command { /// Click a window ref or coordinates #[command(after_help = CLICK_EXAMPLES)] Click { - /// Selector (ref=w1, id=win1, title=Firefox, class=firefox, focused) or x,y coordinates + /// Selector (ref=w1, id=win1, title=Chromium, class=chromium, focused) or x,y coordinates selector: String, }, /// Double-click a window ref or coordinates #[command(after_help = DBLCLICK_EXAMPLES)] Dblclick { - /// Selector (ref=w1, id=win1, title=Firefox, class=firefox, focused) or x,y coordinates + /// Selector (ref=w1, id=win1, title=Chromium, class=chromium, focused) or x,y coordinates selector: String, }, /// Type text into the focused window @@ -81,19 +81,19 @@ pub enum Command { /// Focus a window by ref or name #[command(after_help = FOCUS_EXAMPLES)] Focus { - /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring + /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring selector: String, }, /// Close a window by ref or name #[command(after_help = CLOSE_EXAMPLES)] Close { - /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring + /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring selector: String, }, /// Move a window #[command(after_help = MOVE_WINDOW_EXAMPLES)] MoveWindow { - /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring + /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring selector: String, /// X position x: i32, @@ -103,7 +103,7 @@ pub enum Command { /// Resize a window #[command(after_help = RESIZE_WINDOW_EXAMPLES)] ResizeWindow { - /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring + /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring selector: String, /// Width w: u32, @@ -210,19 +210,19 @@ const SNAPSHOT_EXAMPLES: &str = const LIST_WINDOWS_EXAMPLES: &str = "Examples:\n deskctl list-windows\n deskctl --json list-windows"; const CLICK_EXAMPLES: &str = - "Examples:\n deskctl click @w1\n deskctl click 'title=Firefox'\n deskctl click 500,300"; + "Examples:\n deskctl click @w1\n deskctl click 'title=Chromium'\n deskctl click 500,300"; const DBLCLICK_EXAMPLES: &str = - "Examples:\n deskctl dblclick @w2\n deskctl dblclick 'class=firefox'\n deskctl dblclick 500,300"; + "Examples:\n deskctl dblclick @w2\n deskctl dblclick 'class=chromium'\n deskctl dblclick 500,300"; const TYPE_EXAMPLES: &str = "Examples:\n deskctl type \"hello world\"\n deskctl type \"https://example.com\""; const PRESS_EXAMPLES: &str = "Examples:\n deskctl press enter\n deskctl press escape"; const HOTKEY_EXAMPLES: &str = "Examples:\n deskctl hotkey ctrl l\n deskctl hotkey ctrl shift t"; const FOCUS_EXAMPLES: &str = - "Examples:\n deskctl focus @w1\n deskctl focus 'title=Firefox'\n deskctl focus focused"; + "Examples:\n deskctl focus @w1\n deskctl focus 'title=Chromium'\n deskctl focus focused"; const CLOSE_EXAMPLES: &str = - "Examples:\n deskctl close @w3\n deskctl close 'id=win2'\n deskctl close 'class=firefox'"; + "Examples:\n deskctl close @w3\n deskctl close 'id=win2'\n deskctl close 'class=chromium'"; const MOVE_WINDOW_EXAMPLES: &str = - "Examples:\n deskctl move-window @w1 100 200\n deskctl move-window 'title=Firefox' 0 0"; + "Examples:\n deskctl move-window @w1 100 200\n deskctl move-window 'title=Chromium' 0 0"; const RESIZE_WINDOW_EXAMPLES: &str = "Examples:\n deskctl resize-window @w1 1280 720\n deskctl resize-window 'id=win2' 800 600"; const GET_MONITORS_EXAMPLES: &str = @@ -237,12 +237,12 @@ const GET_MOUSE_POSITION_EXAMPLES: &str = const DOCTOR_EXAMPLES: &str = "Examples:\n deskctl doctor\n deskctl --json doctor"; const UPGRADE_EXAMPLES: &str = "Examples:\n deskctl upgrade\n deskctl upgrade --yes\n deskctl --json upgrade --yes"; -const WAIT_WINDOW_EXAMPLES: &str = "Examples:\n deskctl wait window --selector 'title=Firefox' --timeout 10\n deskctl --json wait window --selector 'class=firefox' --poll-ms 100"; +const WAIT_WINDOW_EXAMPLES: &str = "Examples:\n deskctl wait window --selector 'title=Chromium' --timeout 10\n deskctl --json wait window --selector 'class=chromium' --poll-ms 100"; const WAIT_FOCUS_EXAMPLES: &str = "Examples:\n deskctl wait focus --selector 'id=win3' --timeout 5\n deskctl wait focus --selector focused --poll-ms 200"; const SCREENSHOT_EXAMPLES: &str = "Examples:\n deskctl screenshot\n deskctl screenshot /tmp/screen.png\n deskctl screenshot --annotate"; const LAUNCH_EXAMPLES: &str = - "Examples:\n deskctl launch firefox\n deskctl launch code -- --new-window"; + "Examples:\n deskctl launch chromium\n deskctl launch code -- --new-window"; const MOUSE_MOVE_EXAMPLES: &str = "Examples:\n deskctl mouse move 500 300\n deskctl mouse move 0 0"; const MOUSE_SCROLL_EXAMPLES: &str = @@ -277,7 +277,7 @@ pub enum WaitCmd { #[derive(Args)] pub struct WaitSelectorOpts { - /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring + /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring #[arg(long)] pub selector: String, @@ -1103,8 +1103,8 @@ mod tests { "windows": [{ "ref_id": "w1", "window_id": "win1", - "title": "Firefox", - "app_name": "firefox", + "title": "Chromium", + "app_name": "chromium", "x": 0, "y": 0, "width": 1280, @@ -1125,37 +1125,37 @@ mod tests { fn action_text_includes_target_identity() { let lines = render_success_lines( &Command::Focus { - selector: "title=Firefox".to_string(), + selector: "title=Chromium".to_string(), }, Some(&json!({ "action": "focus", - "window": "Firefox", - "title": "Firefox", + "window": "Chromium", + "title": "Chromium", "ref_id": "w2", "window_id": "win7" })), ) .unwrap(); - assert_eq!(lines, vec!["Focused @w2 [win7] \"Firefox\""]); + assert_eq!(lines, vec!["Focused @w2 [win7] \"Chromium\""]); } #[test] fn timeout_errors_render_last_observation() { let lines = render_error_lines(&Response::err_with_data( - "Timed out waiting for focus to match selector: title=Firefox", + "Timed out waiting for focus to match selector: title=Chromium", json!({ "kind": "timeout", "wait": "focus", - "selector": "title=Firefox", + "selector": "title=Chromium", "timeout_ms": 1000, "last_observation": { "kind": "window_not_focused", "window": { "ref_id": "w1", "window_id": "win1", - "title": "Firefox", - "app_name": "firefox", + "title": "Chromium", + "app_name": "chromium", "x": 0, "y": 0, "width": 1280, @@ -1167,10 +1167,8 @@ mod tests { }), )); - assert!(lines - .iter() - .any(|line| line - .contains("Timed out after 1000ms waiting for focus selector title=Firefox"))); + assert!(lines.iter().any(|line| line + .contains("Timed out after 1000ms waiting for focus selector title=Chromium"))); assert!(lines .iter() .any(|line| line.contains("matching window exists but is not focused yet"))); @@ -1190,9 +1188,9 @@ mod tests { let summary = target_summary(&json!({ "ref_id": "w1", "window_id": "win1", - "title": "Firefox" + "title": "Chromium" })); - assert_eq!(summary.as_deref(), Some("@w1 [win1] \"Firefox\"")); + assert_eq!(summary.as_deref(), Some("@w1 [win1] \"Chromium\"")); } #[test] diff --git a/src/core/refs.rs b/src/core/refs.rs index 34e1ba7..7fd7b6c 100644 --- a/src/core/refs.rs +++ b/src/core/refs.rs @@ -412,8 +412,8 @@ mod tests { SelectorQuery::WindowId("win4".to_string()) ); assert_eq!( - SelectorQuery::parse("title=Firefox"), - SelectorQuery::Title("Firefox".to_string()) + SelectorQuery::parse("title=Chromium"), + SelectorQuery::Title("Chromium".to_string()) ); assert_eq!( SelectorQuery::parse("class=Navigator"), @@ -458,11 +458,11 @@ mod tests { fn fuzzy_resolution_fails_with_candidates_when_ambiguous() { let mut refs = RefMap::new(); refs.rebuild(&[ - sample_window(1, "Firefox"), + sample_window(1, "Chromium"), BackendWindow { native_id: 2, - title: "Firefox Settings".to_string(), - app_name: "Firefox".to_string(), + title: "Chromium Settings".to_string(), + app_name: "Chromium".to_string(), x: 0, y: 0, width: 10, @@ -472,7 +472,7 @@ mod tests { }, ]); - match refs.resolve("firefox") { + match refs.resolve("chromium") { ResolveResult::Ambiguous { mode, candidates, .. } => { From 3a8d9f90c1ac036cfd5bdb30daf7275909870dd9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Mar 2026 19:31:47 +0000 Subject: [PATCH 55/64] release: v0.1.12 [skip ci] --- Cargo.lock | 2 +- Cargo.toml | 2 +- npm/deskctl/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b411e80..4acd174 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deskctl" -version = "0.1.11" +version = "0.1.12" dependencies = [ "ab_glyph", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 59108df..d782ecd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deskctl" -version = "0.1.11" +version = "0.1.12" edition = "2021" description = "X11 desktop control CLI for agents" license = "MIT" diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json index 60b8ed4..1dd5bff 100644 --- a/npm/deskctl/package.json +++ b/npm/deskctl/package.json @@ -1,6 +1,6 @@ { "name": "deskctl", - "version": "0.1.11", + "version": "0.1.12", "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", From 3ca6c90eafc6020b99730904a70e5f1593ca8441 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Fri, 27 Mar 2026 00:20:37 -0400 Subject: [PATCH 56/64] fix termination bug --- src/daemon/mod.rs | 58 +++++++++++++++++++++++++++++++------------- tests/support/mod.rs | 30 +++++++++++++++++++++++ tests/x11_runtime.rs | 25 +++++++++++++++++++ 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 3df1d9a..9e7e931 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -1,6 +1,7 @@ mod handler; mod state; +use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::{Context, Result}; @@ -12,6 +13,29 @@ use crate::core::paths::{pid_path_from_env, socket_path_from_env}; use crate::core::session; use state::DaemonState; +struct RuntimePathsGuard { + socket_path: PathBuf, + pid_path: Option, +} + +impl RuntimePathsGuard { + fn new(socket_path: PathBuf, pid_path: Option) -> Self { + Self { + socket_path, + pid_path, + } + } +} + +impl Drop for RuntimePathsGuard { + fn drop(&mut self) { + remove_runtime_path(&self.socket_path); + if let Some(ref pid_path) = self.pid_path { + remove_runtime_path(pid_path); + } + } +} + pub fn run() -> Result<()> { // Validate session before starting session::detect_session()?; @@ -25,7 +49,6 @@ pub fn run() -> Result<()> { async fn async_run() -> Result<()> { let socket_path = socket_path_from_env().context("DESKCTL_SOCKET_PATH not set")?; - let pid_path = pid_path_from_env(); // Clean up stale socket @@ -33,20 +56,21 @@ async fn async_run() -> Result<()> { std::fs::remove_file(&socket_path)?; } - // Write PID file - if let Some(ref pid_path) = pid_path { - std::fs::write(pid_path, std::process::id().to_string())?; - } - - let listener = UnixListener::bind(&socket_path) - .context(format!("Failed to bind socket: {}", socket_path.display()))?; - let session = std::env::var("DESKCTL_SESSION").unwrap_or_else(|_| "default".to_string()); let state = Arc::new(Mutex::new( DaemonState::new(session, socket_path.clone()) .context("Failed to initialize daemon state")?, )); + let listener = UnixListener::bind(&socket_path) + .context(format!("Failed to bind socket: {}", socket_path.display()))?; + let _runtime_paths = RuntimePathsGuard::new(socket_path.clone(), pid_path.clone()); + + // Write PID file only after the daemon is ready to serve requests. + if let Some(ref pid_path) = pid_path { + std::fs::write(pid_path, std::process::id().to_string())?; + } + let shutdown = Arc::new(tokio::sync::Notify::new()); let shutdown_clone = shutdown.clone(); @@ -75,14 +99,6 @@ async fn async_run() -> Result<()> { } } - // Cleanup - if socket_path.exists() { - let _ = std::fs::remove_file(&socket_path); - } - if let Some(ref pid_path) = pid_path { - let _ = std::fs::remove_file(pid_path); - } - Ok(()) } @@ -123,3 +139,11 @@ async fn handle_connection( Ok(()) } + +fn remove_runtime_path(path: &Path) { + if let Err(error) = std::fs::remove_file(path) { + if error.kind() != std::io::ErrorKind::NotFound { + eprintln!("Failed to remove runtime path {}: {error}", path.display()); + } + } +} diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 5c6f0be..719334d 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -142,6 +142,10 @@ impl TestSession { .expect("TestSession always has an explicit socket path") } + pub fn pid_path(&self) -> PathBuf { + self.root.join("deskctl.pid") + } + pub fn create_stale_socket(&self) -> Result<()> { let listener = UnixListener::bind(self.socket_path()) .with_context(|| format!("Failed to bind {}", self.socket_path().display()))?; @@ -187,6 +191,29 @@ impl TestSession { ) }) } + + pub fn run_daemon(&self, env: I) -> Result + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + let mut command = Command::new(env!("CARGO_BIN_EXE_deskctl")); + command + .env("DESKCTL_DAEMON", "1") + .env("DESKCTL_SOCKET_PATH", self.socket_path()) + .env("DESKCTL_PID_PATH", self.pid_path()) + .env("DESKCTL_SESSION", &self.opts.session) + .envs(env); + + command.output().with_context(|| { + format!( + "Failed to run daemon {} against {}", + env!("CARGO_BIN_EXE_deskctl"), + self.socket_path().display() + ) + }) + } } impl Drop for TestSession { @@ -195,6 +222,9 @@ impl Drop for TestSession { if self.socket_path().exists() { let _ = std::fs::remove_file(self.socket_path()); } + if self.pid_path().exists() { + let _ = std::fs::remove_file(self.pid_path()); + } let _ = std::fs::remove_dir_all(&self.root); } } diff --git a/tests/x11_runtime.rs b/tests/x11_runtime.rs index 2aac58c..30308cb 100644 --- a/tests/x11_runtime.rs +++ b/tests/x11_runtime.rs @@ -114,6 +114,31 @@ fn daemon_start_recovers_from_stale_socket() -> Result<()> { Ok(()) } +#[test] +fn daemon_init_failure_cleans_runtime_state() -> Result<()> { + let _guard = env_lock_guard(); + let session = TestSession::new("daemon-init-failure")?; + + let output = session.run_daemon([("XDG_SESSION_TYPE", "x11"), ("DISPLAY", ":99999")])?; + assert!(!output.status.success(), "daemon startup should fail"); + + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("Failed to initialize daemon state"), + "unexpected stderr: {stderr}" + ); + assert!( + !session.socket_path().exists(), + "failed startup should remove the socket path" + ); + assert!( + !session.pid_path().exists(), + "failed startup should remove the pid path" + ); + + Ok(()) +} + #[test] fn wait_window_returns_matched_window_payload() -> Result<()> { let _guard = env_lock_guard(); From 9bfada8b4bb06a74d9e45a3f332efb949b02d2ff Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Fri, 27 Mar 2026 10:04:10 -0400 Subject: [PATCH 57/64] fix helper --- tests/support/mod.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 719334d..07cc5a7 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -4,6 +4,7 @@ use std::os::unix::net::UnixListener; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::sync::{Mutex, OnceLock}; +use std::thread; use std::time::{SystemTime, UNIX_EPOCH}; use anyhow::{anyhow, bail, Context, Result}; @@ -60,8 +61,7 @@ pub struct FixtureWindow { impl FixtureWindow { pub fn create(title: &str, app_class: &str) -> Result { - let (conn, screen_num) = - x11rb::connect(None).context("Failed to connect to the integration test display")?; + let (conn, screen_num) = connect_to_test_display()?; let screen = &conn.setup().roots[screen_num]; let window = conn.generate_id()?; @@ -103,6 +103,26 @@ impl FixtureWindow { } } +fn connect_to_test_display() -> Result<(RustConnection, usize)> { + let max_attempts = 10; + let mut last_error = None; + + for attempt in 0..max_attempts { + match x11rb::connect(None) { + Ok(connection) => return Ok(connection), + Err(error) => { + last_error = Some(anyhow!(error)); + if attempt + 1 < max_attempts { + thread::sleep(std::time::Duration::from_millis(100 * (attempt + 1) as u64)); + } + } + } + } + + Err(last_error.expect("x11 connection attempts should capture an error")) + .context("Failed to connect to the integration test display") +} + impl Drop for FixtureWindow { fn drop(&mut self) { let _ = self.conn.destroy_window(self.window); From 85e191663547943b5a3468f48ecba45768271f74 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 14:10:07 +0000 Subject: [PATCH 58/64] release: v0.1.13 [skip ci] --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- npm/deskctl/package.json | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4acd174..c948da0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,9 +241,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "jobserver", @@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deskctl" -version = "0.1.12" +version = "0.1.13" dependencies = [ "ab_glyph", "anyhow", @@ -1039,9 +1039,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -1699,9 +1699,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd_helpers" @@ -1861,9 +1861,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -2297,9 +2297,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index d782ecd..2c4745c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deskctl" -version = "0.1.12" +version = "0.1.13" edition = "2021" description = "X11 desktop control CLI for agents" license = "MIT" diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json index 1dd5bff..327fb33 100644 --- a/npm/deskctl/package.json +++ b/npm/deskctl/package.json @@ -1,6 +1,6 @@ { "name": "deskctl", - "version": "0.1.12", + "version": "0.1.13", "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", From 2107449d9bf1425de3c43d1036465cbed69535cf Mon Sep 17 00:00:00 2001 From: Hari <73809867+harivansh-afk@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:17:51 -0400 Subject: [PATCH 59/64] Update README with asset link and description change Added a link to GitHub assets and removed 'Linux' from description. --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 935f329..dccbe04 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # deskctl - [![npm](https://img.shields.io/npm/v/deskctl?label=npm)](https://www.npmjs.com/package/deskctl) [![skill](https://img.shields.io/badge/skills.sh-deskctl-111827)](skills/deskctl) -Desktop control cli for AI agents on Linux X11. +Desktop control cli for AI agents on X11. + +https://github.com/user-attachments/assets/e820787e-4d1a-463f-bdcf-a829588778bf + ## Install From 19669fb4c14462abd2cfc864328cb9c3c2143bf0 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Fri, 27 Mar 2026 19:25:26 -0400 Subject: [PATCH 60/64] demo --- demo/index.html | 969 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 969 insertions(+) create mode 100644 demo/index.html diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..70ac230 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,969 @@ + + + + + +deskctl - Desktop Control for AI Agents + + + + +
+

deskctl

+

desktop control CLI for AI agents

+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+
+ Files ~/reports +
+
+
+
+ 📝 + task_brief.txt + 2.1 KB +
+
+ 📊 + nvda_q1_data.csv + 48 KB +
+
+ 📄 + prev_report.pdf + 1.2 MB +
+
+ 📁 + archive/ + -- +
+
+
+ task: Prepare NVDA Q1 earnings summary
+ source: finance.yahoo.com, local csv
+ output: Google Docs report with chart +
+
+
+ + +
+
+
+ Chrome - Yahoo Finance +
+
+
+ NVDA + $924.68 + +3.42% + 1Y +
+
+ + + + + + + + + + + + + $950 + $800 + $650 + +
+
+
+
+ + +
+
+
+ Chrome - Google Docs +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + NVDA 1Y + +
+
+
+
+ + +
@w1
+
@w2
+
@w3
+ +
+ +
+ +
+
+ + +
+
Files
+
Yahoo Finance
+
Google Docs
+
+
+
+ +
+
+
+
+
+ agent computer +
+
+
+
+ +
+

AI agent controlling a live desktop via deskctl

+ +
+ + + + From 2b3d422c7b0d40b5523a07d3f2c3c81b5fb42702 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Fri, 27 Mar 2026 19:40:24 -0400 Subject: [PATCH 61/64] crates.io --- .github/workflows/ci.yml | 54 +++++++++++++++++++++++++++----------- docs/releasing.md | 10 +++---- site/src/pages/index.astro | 3 +++ 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcef6fb..0bcc90c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ on: publish_crates: description: Publish to crates.io type: boolean - default: false + default: true env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true @@ -309,27 +309,25 @@ jobs: artifacts/checksums.txt fi - publish: - name: Publish + publish-npm: + name: Publish npm needs: [changes, update-manifests, release] - if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true' + if: >- + github.event_name != 'pull_request' + && needs.changes.outputs.rust == 'true' + && (inputs.publish_npm == true || inputs.publish_npm == '') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ needs.changes.outputs.tag }} - - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-node@v4 with: node-version: 22 registry-url: https://registry.npmjs.org - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev - - - name: Check current published state + - name: Check if already published id: published run: | VERSION="${{ needs.changes.outputs.version }}" @@ -338,13 +336,9 @@ jobs: else echo "npm=false" >> "$GITHUB_OUTPUT" fi - if curl -fsSL "https://crates.io/api/v1/crates/deskctl/${VERSION}" >/dev/null 2>&1; then - echo "crates=true" >> "$GITHUB_OUTPUT" - else - echo "crates=false" >> "$GITHUB_OUTPUT" - fi - name: Validate npm package + if: steps.published.outputs.npm != 'true' run: node npm/deskctl/scripts/validate-package.js - name: Publish npm @@ -353,8 +347,36 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: npm publish ./npm/deskctl --access public + publish-crates: + name: Publish crates.io + needs: [changes, update-manifests, release] + if: >- + github.event_name != 'pull_request' + && needs.changes.outputs.rust == 'true' + && (inputs.publish_crates == true || inputs.publish_crates == '') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.changes.outputs.tag }} + + - uses: dtolnay/rust-toolchain@stable + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev + + - name: Check if already published + id: published + run: | + VERSION="${{ needs.changes.outputs.version }}" + if curl -fsSL "https://crates.io/api/v1/crates/deskctl/${VERSION}" >/dev/null 2>&1; then + echo "crates=true" >> "$GITHUB_OUTPUT" + else + echo "crates=false" >> "$GITHUB_OUTPUT" + fi + - name: Publish crates.io - if: inputs.publish_crates && steps.published.outputs.crates != 'true' + if: steps.published.outputs.crates != 'true' env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: cargo publish --locked diff --git a/docs/releasing.md b/docs/releasing.md index 8f39d3f..849d661 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -59,12 +59,12 @@ The repository release workflow: - publishes the canonical GitHub Release asset - uploads `checksums.txt` -The registry publish workflow: +The registry publish jobs (npm and crates.io run in parallel): -- targets an existing release tag -- checks that Cargo, npm, and the requested tag all agree on version -- checks whether that version is already published on npm and crates.io -- only publishes the channels explicitly requested +- target an existing release tag +- check whether that version is already published on the respective registry +- skip already-published versions +- both default to enabled; can be toggled via workflow_dispatch inputs ## Rerun Safety diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index b914e16..8dfde01 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -43,6 +43,9 @@ import DocLayout from "../layouts/DocLayout.astro";
  • GitHub
  • +
  • + crates.io +
  • npm
  • From 2b7de5fceff991d02308081161079fa1f6176aad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 23:46:33 +0000 Subject: [PATCH 62/64] release: v0.1.14 [skip ci] --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- npm/deskctl/package.json | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c948da0..eb0e2ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deskctl" -version = "0.1.13" +version = "0.1.14" dependencies = [ "ab_glyph", "anyhow", @@ -911,9 +911,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" dependencies = [ "once_cell", "wasm-bindgen", @@ -1907,9 +1907,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" dependencies = [ "cfg-if", "once_cell", @@ -1920,9 +1920,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1930,9 +1930,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" dependencies = [ "bumpalo", "proc-macro2", @@ -1943,9 +1943,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 2c4745c..be051c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deskctl" -version = "0.1.13" +version = "0.1.14" edition = "2021" description = "X11 desktop control CLI for agents" license = "MIT" diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json index 327fb33..c676924 100644 --- a/npm/deskctl/package.json +++ b/npm/deskctl/package.json @@ -1,6 +1,6 @@ { "name": "deskctl", - "version": "0.1.13", + "version": "0.1.14", "description": "Installable deskctl package for Linux X11 agents", "license": "MIT", "homepage": "https://github.com/harivansh-afk/deskctl", From 13119eecf7cd96024ac4f0e3f435f4eb45d2759f Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 2 Apr 2026 14:53:56 -0400 Subject: [PATCH 63/64] update crates.io link --- site/src/pages/index.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 8dfde01..478c7a2 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -44,7 +44,7 @@ import DocLayout from "../layouts/DocLayout.astro"; GitHub
  • - crates.io + crates.io
  • npm From 32c6d337f102a2d68de6e7d30ac9e2a8162ff010 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Sun, 5 Apr 2026 11:46:45 -0400 Subject: [PATCH 64/64] ci: use self-hosted netty runners for validation jobs --- .github/workflows/ci.yml | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bcc90c..1c2e7f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,5 @@ name: CI -# Runners: uvacompute (https://uvacompute.com) -# To enable, set the UVA_RUNNER repo variable to the correct runner label. -# runs-on: ${{ vars.UVA_RUNNER || 'ubuntu-latest' }} - on: pull_request: branches: [main] @@ -37,7 +33,7 @@ permissions: jobs: changes: name: Changes - runs-on: ubuntu-latest + runs-on: [self-hosted, netty] outputs: rust: ${{ steps.check.outputs.rust }} version: ${{ steps.version.outputs.version }} @@ -105,7 +101,7 @@ jobs: name: Validate needs: changes if: needs.changes.outputs.rust == 'true' - runs-on: ubuntu-latest + runs-on: [self-hosted, netty] steps: - uses: actions/checkout@v4 @@ -129,9 +125,6 @@ jobs: - name: Install site dependencies run: pnpm --dir site install --frozen-lockfile - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev - - name: Format check run: make fmt-check @@ -148,7 +141,7 @@ jobs: name: Integration (Xvfb) needs: changes if: needs.changes.outputs.rust == 'true' - runs-on: ubuntu-latest + runs-on: [self-hosted, netty] steps: - uses: actions/checkout@v4 @@ -156,9 +149,6 @@ jobs: - uses: Swatinem/rust-cache@v2 - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev xvfb - - name: Xvfb integration tests run: make test-integration @@ -166,7 +156,7 @@ jobs: name: Distribution Validate needs: changes if: needs.changes.outputs.rust == 'true' - runs-on: ubuntu-latest + runs-on: [self-hosted, netty] steps: - uses: actions/checkout@v4 @@ -178,19 +168,11 @@ jobs: with: node-version: 22 - - uses: cachix/install-nix-action@v30 - with: - extra_nix_config: | - experimental-features = nix-command flakes - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev - - name: Distribution validation run: make dist-validate # --- Release pipeline: update-manifests -> build -> release -> publish --- - # Version bump happens BEFORE build so the binary has the correct version. + # These stay on ubuntu-latest for artifact upload/download and registry publishing. update-manifests: name: Update Manifests