diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1c2e7f4..fd5d7e3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,231 +1,20 @@
name: CI
on:
- pull_request:
- branches: [main]
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: true
-
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: write
+ packages: write
jobs:
- changes:
- name: Changes
- runs-on: [self-hosted, netty]
- outputs:
- rust: ${{ steps.check.outputs.rust }}
- version: ${{ steps.version.outputs.version }}
- tag: ${{ steps.version.outputs.tag }}
+ cargo:
+ name: Cargo Build
+ runs-on: ${{ vars.UVA_RUNNER || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - uses: dorny/paths-filter@v3
- id: filter
- with:
- filters: |
- rust:
- - 'src/**'
- - 'tests/**'
- - 'Cargo.toml'
- - 'Cargo.lock'
- - 'npm/**'
- - 'flake.nix'
- - 'flake.lock'
- - 'docker/**'
- - '.github/workflows/**'
- - 'Makefile'
-
- - 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
-
- - name: Calculate next version
- id: version
- if: github.event_name != 'pull_request' && steps.check.outputs.rust == 'true'
- run: |
- 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
- needs: changes
- if: needs.changes.outputs.rust == 'true'
- runs-on: [self-hosted, netty]
- steps:
- - uses: actions/checkout@v4
-
- - uses: dtolnay/rust-toolchain@stable
- with:
- components: clippy
-
- - uses: Swatinem/rust-cache@v2
-
- - uses: pnpm/action-setup@v4
- with:
- version: 10
- run_install: false
-
- - uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
- cache-dependency-path: site/pnpm-lock.yaml
-
- - name: Install site dependencies
- run: pnpm --dir site install --frozen-lockfile
-
- - name: Format check
- run: make fmt-check
-
- - name: Clippy
- run: make lint
-
- - name: Unit tests
- run: make test-unit
-
- - name: Site format check
- run: make site-format-check
-
- integration:
- name: Integration (Xvfb)
- needs: changes
- if: needs.changes.outputs.rust == 'true'
- runs-on: [self-hosted, netty]
- steps:
- - uses: actions/checkout@v4
-
- - uses: dtolnay/rust-toolchain@stable
-
- - uses: Swatinem/rust-cache@v2
-
- - name: Xvfb integration tests
- run: make test-integration
-
- distribution:
- name: Distribution Validate
- needs: changes
- if: needs.changes.outputs.rust == 'true'
- runs-on: [self-hosted, netty]
- steps:
- - uses: actions/checkout@v4
-
- - uses: dtolnay/rust-toolchain@stable
-
- - uses: Swatinem/rust-cache@v2
-
- - uses: actions/setup-node@v4
- with:
- node-version: 22
-
- - name: Distribution validation
- run: make dist-validate
-
- # --- Release pipeline: update-manifests -> build -> release -> publish ---
- # These stay on ubuntu-latest for artifact upload/download and registry publishing.
-
- update-manifests:
- name: 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:
- 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
-
- 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:
@@ -236,16 +25,6 @@ jobs:
- 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
@@ -258,107 +37,72 @@ jobs:
path: target/release/deskctl
retention-days: 7
+ docker:
+ name: Docker Build
+ runs-on: ${{ vars.UVA_RUNNER || '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
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: docker/Dockerfile
+ push: true
+ tags: |
+ ghcr.io/${{ github.repository }}:latest
+ ghcr.io/${{ github.repository }}:v${{ steps.version.outputs.version }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
release:
name: Release
- needs: [changes, build, update-manifests]
- if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
+ needs: [cargo, docker]
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: actions/download-artifact@v4
with:
name: deskctl-linux-x86_64
path: artifacts/
- - name: Create release
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Prepare release assets
run: |
chmod +x artifacts/deskctl
mv artifacts/deskctl artifacts/deskctl-linux-x86_64
- cd artifacts && sha256sum deskctl-linux-x86_64 > checksums.txt && cd ..
- if gh release view "${{ needs.changes.outputs.tag }}" >/dev/null 2>&1; then
- gh release upload "${{ needs.changes.outputs.tag }}" \
- artifacts/deskctl-linux-x86_64 \
- artifacts/checksums.txt \
- --clobber
- else
- gh release create "${{ needs.changes.outputs.tag }}" \
- --title "${{ needs.changes.outputs.tag }}" \
- --generate-notes \
- artifacts/deskctl-linux-x86_64 \
- artifacts/checksums.txt
- fi
- publish-npm:
- name: Publish npm
- needs: [changes, update-manifests, release]
- 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: actions/setup-node@v4
- with:
- node-version: 22
- registry-url: https://registry.npmjs.org
-
- - name: Check if already published
- 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
-
- - name: Validate npm package
- if: steps.published.outputs.npm != 'true'
- run: node npm/deskctl/scripts/validate-package.js
-
- - name: Publish npm
- if: steps.published.outputs.npm != 'true'
+ - name: Create or update release
env:
- 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
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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
+ TAG="v${{ steps.version.outputs.version }}"
+ IMAGE="ghcr.io/${{ github.repository }}:${TAG}"
- - name: Publish crates.io
- if: steps.published.outputs.crates != 'true'
- env:
- CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- run: cargo publish --locked
+ 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
diff --git a/.gitignore b/.gitignore
index 40542a9..e1b69c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,2 @@
target/
-tmp/
-.vercel
-secret/
-.claude/
-.codex/
-openspec/
-npm/deskctl/vendor/
-npm/deskctl/*.tgz
+.humanlayer/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index 34d4298..0000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-repos:
- - repo: local
- hooks:
- - id: rustfmt
- name: rustfmt
- entry: cargo fmt --
- language: system
- files: \.rs$
- pass_filenames: true
- - id: site-format-check
- name: site format check
- entry: make site-format-check
- language: system
- files: ^site/
- pass_filenames: false
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 97e8c7c..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# Contributing
-
-## Prerequisites
-
-- Rust toolchain
-- `make`
-- `pre-commit` for commit-time hooks
-- `pnpm` for site formatting checks
-- Linux with `xvfb-run` for integration tests
-
-Install site dependencies before running site checks:
-
-```bash
-pnpm --dir site install
-```
-
-## Repository Layout
-
-- `src/lib.rs` exposes the library target used by integration tests
-- `src/main.rs` is the thin CLI binary wrapper
-- `src/` holds production code and unit tests
-- `tests/` holds integration tests
-- `tests/support/` holds shared X11 and daemon helpers for integration coverage
-- `docs/runtime-contract.md` is the stable-vs-best-effort runtime output contract for agent-facing CLI work
-
-Keep integration-only helpers out of `src/`.
-
-## Local Validation
-
-The repo uses one local validation surface through `make`:
-
-```bash
-make fmt-check
-make lint
-make test-unit
-make test-integration
-make site-format-check
-make cargo-publish-dry-run
-make npm-package-check
-make nix-flake-check
-make dist-validate
-make validate
-```
-
-`make validate` runs the full Phase 2 validation stack. It requires Linux, `xvfb-run`, and site dependencies to be installed.
-`make dist-validate` runs the distribution validation stack. It requires `npm`, `nix`, and Linux for the full npm runtime smoke path.
-
-## Pre-commit Hooks
-
-Install the hook workflow once:
-
-```bash
-pre-commit install
-```
-
-Run hooks across the repo on demand:
-
-```bash
-pre-commit run --all-files
-```
-
-The hook config intentionally stays small:
-
-- Rust files use default `rustfmt`
-- Site files reuse the existing `site/` Prettier setup
-- Slower checks stay in CI or `make validate`
-
-## Distribution Work
-
-Distribution support currently ships through:
-
-- crate: `deskctl`
-- npm package: `deskctl`
-- repo flake: `flake.nix`
-- command name on every channel: `deskctl`
-
-For maintainer release and publish steps, see [docs/releasing.md](docs/releasing.md).
-
-Source-build and packaging work should keep Docker as a local Linux build convenience, not as the canonical registry release path.
-
-## Integration Tests
-
-Integration coverage is Linux/X11-only in this phase. The supported local entrypoint is:
-
-```bash
-make test-integration
-```
-
-That command runs the top-level X11 integration tests under `xvfb-run` with one test thread so the shared display/session environment stays deterministic.
diff --git a/Cargo.lock b/Cargo.lock
index eb0e2ce..958922a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "ab_glyph"
@@ -241,9 +241,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
-version = "1.2.58"
+version = "1.2.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
+checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "deskctl"
-version = "0.1.14"
+version = "0.1.0"
dependencies = [
"ab_glyph",
"anyhow",
@@ -911,9 +911,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.92"
+version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995"
+checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -1039,9 +1039,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.2.0"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
@@ -1699,9 +1699,9 @@ dependencies = [
[[package]]
name = "simd-adler32"
-version = "0.3.9"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "simd_helpers"
@@ -1861,9 +1861,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.23.0"
+version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
+checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
dependencies = [
"getrandom 0.4.2",
"js-sys",
@@ -1907,9 +1907,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.115"
+version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a"
+checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
dependencies = [
"cfg-if",
"once_cell",
@@ -1920,9 +1920,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.115"
+version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67"
+checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1930,9 +1930,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.115"
+version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf"
+checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1943,9 +1943,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.115"
+version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93"
+checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
dependencies = [
"unicode-ident",
]
@@ -2297,9 +2297,9 @@ dependencies = [
[[package]]
name = "zune-jpeg"
-version = "0.5.15"
+version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
+checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6"
dependencies = [
"zune-core",
]
diff --git a/Cargo.toml b/Cargo.toml
index be051c7..bef1cfd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,23 +1,10 @@
[package]
name = "deskctl"
-version = "0.1.14"
+version = "0.1.0"
edition = "2021"
description = "X11 desktop control CLI for agents"
license = "MIT"
repository = "https://github.com/harivansh-afk/deskctl"
-homepage = "https://github.com/harivansh-afk/deskctl"
-readme = "README.md"
-keywords = ["x11", "desktop", "automation", "cli", "agent"]
-categories = ["command-line-utilities"]
-rust-version = "1.75"
-include = [
- "/Cargo.toml",
- "/Cargo.lock",
- "/README.md",
- "/LICENCE",
- "/assets/**",
- "/src/**",
-]
[dependencies]
clap = { version = "4", features = ["derive", "env"] }
diff --git a/LICENCE b/LICENCE
deleted file mode 100644
index 2f23cfe..0000000
--- a/LICENCE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2026 Harivansh Rathi
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 7e1f852..0000000
--- a/Makefile
+++ /dev/null
@@ -1,63 +0,0 @@
-.PHONY: fmt fmt-check lint test-unit test-integration site-format-check cargo-publish-dry-run npm-package-check nix-flake-check dist-validate validate
-
-fmt:
- cargo fmt --all
-
-fmt-check:
- cargo fmt --all --check
-
-lint:
- cargo clippy --all-targets -- -D warnings
-
-test-unit:
- cargo test --lib
-
-test-integration:
- @if [ "$$(uname -s)" != "Linux" ]; then \
- echo "Integration tests require Linux and xvfb-run."; \
- exit 1; \
- fi
- @if ! command -v xvfb-run >/dev/null 2>&1; then \
- echo "xvfb-run is required to execute integration tests."; \
- exit 1; \
- fi
- XDG_SESSION_TYPE=x11 xvfb-run -a cargo test --test x11_runtime -- --test-threads=1
-
-site-format-check:
- @if ! command -v pnpm >/dev/null 2>&1; then \
- echo "pnpm is required for site formatting checks."; \
- exit 1; \
- fi
- pnpm --dir site format:check
-
-cargo-publish-dry-run:
- cargo publish --dry-run --allow-dirty --locked
-
-npm-package-check:
- @if ! command -v npm >/dev/null 2>&1; then \
- echo "npm is required for npm packaging validation."; \
- exit 1; \
- fi
- 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 --pack-destination ./tmp/npm-pack >/dev/null
- @if [ "$$(uname -s)" != "Linux" ]; then \
- echo "Skipping npm package runtime smoke test on non-Linux host."; \
- else \
- cargo build && \
- PACK_TGZ=$$(ls ./tmp/npm-pack/*.tgz | head -n 1) && \
- DESKCTL_BINARY_PATH="$$(pwd)/target/debug/deskctl" npm install --prefix ./tmp/npm-install "$${PACK_TGZ}" && \
- ./tmp/npm-install/node_modules/.bin/deskctl --version; \
- fi
-
-nix-flake-check:
- @if ! command -v nix >/dev/null 2>&1; then \
- echo "nix is required for flake validation."; \
- exit 1; \
- fi
- nix flake check
-
-dist-validate: test-unit cargo-publish-dry-run npm-package-check nix-flake-check
-
-validate: fmt-check lint test-unit test-integration site-format-check
diff --git a/README.md b/README.md
index dccbe04..fb9dcd0 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,75 @@
# deskctl
-[](https://www.npmjs.com/package/deskctl)
-[](skills/deskctl)
-
-Desktop control cli for AI agents on X11.
-
-https://github.com/user-attachments/assets/e820787e-4d1a-463f-bdcf-a829588778bf
+Desktop control CLI for AI agents on Linux X11.
## Install
```bash
-npm install -g deskctl
+cargo install deskctl
```
+Build a Linux binary with Docker:
+
```bash
-deskctl doctor
-deskctl snapshot --annotate
+docker compose -f docker/docker-compose.yml run --rm build
```
-## Skill
+This writes `dist/deskctl-linux-x86_64`.
+
+Copy it to an SSH machine where `scp` is unavailable:
```bash
-npx skills add harivansh-afk/deskctl
+ssh -p 443 deskctl@ssh.agentcomputer.ai 'cat > ~/deskctl && chmod +x ~/deskctl' < dist/deskctl-linux-x86_64
```
-## Docs
-
-- runtime contract: [docs/runtime-contract.md](docs/runtime-contract.md)
-- releasing: [docs/releasing.md](docs/releasing.md)
-- contributing: [CONTRIBUTING.md](CONTRIBUTING.md)
-
-## Install paths
-
-Nix:
+Run it on an X11 session:
```bash
-nix run github:harivansh-afk/deskctl -- --help
-nix profile install github:harivansh-afk/deskctl
+DISPLAY=:1 XDG_SESSION_TYPE=x11 ~/deskctl --json snapshot --annotate
```
-Rust:
-
+Local source build requirements:
```bash
cargo build
```
+
+At the moment there are no extra native build dependencies beyond a Rust toolchain.
+
+## Quick Start
+
+```bash
+# See the desktop
+deskctl snapshot
+
+# Click a window
+deskctl click @w1
+
+# Type text
+deskctl type "hello world"
+
+# Focus by name
+deskctl focus "firefox"
+```
+
+## Architecture
+
+Client-daemon architecture over Unix sockets (NDJSON wire protocol).
+The daemon starts automatically on first command and keeps the X11 connection alive for fast repeated calls.
+
+## Runtime Requirements
+
+- Linux with X11 session
+- Rust 1.75+ (for build)
+
+The binary itself only links the standard glibc runtime on Linux (`libc`, `libm`, `libgcc_s`).
+
+For deskctl to be fully functional on a fresh VM you still need:
+
+- an X11 server and an active `DISPLAY`
+- `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`
+- an X server with the extensions needed for input simulation and screen metadata, which is standard on normal desktop X11 setups
+
+## 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.
diff --git a/SKILL.md b/SKILL.md
new file mode 100644
index 0000000..0407b0f
--- /dev/null
+++ b/SKILL.md
@@ -0,0 +1,116 @@
+---
+name: deskctl
+description: Desktop control CLI for AI agents
+allowed-tools: Bash(deskctl:*)
+---
+
+# deskctl
+
+Desktop control CLI for AI agents on Linux X11. Provides a unified interface for screenshots, mouse/keyboard input, and window management with compact `@wN` window references.
+
+## Core Workflow
+
+1. **Snapshot** to see the desktop and get window refs
+2. **Act** using refs or coordinates (click, type, focus)
+3. **Repeat** as needed
+
+## Quick Reference
+
+### See the Desktop
+
+```bash
+deskctl snapshot # Screenshot + window tree with @wN refs
+deskctl snapshot --annotate # Screenshot with bounding boxes and labels
+deskctl snapshot --json # Structured JSON output
+deskctl list-windows # Window tree without screenshot
+deskctl screenshot /tmp/s.png # Screenshot only (no window tree)
+```
+
+### Click and Type
+
+```bash
+deskctl click @w1 # Click center of window @w1
+deskctl click 500,300 # Click absolute coordinates
+deskctl dblclick @w2 # Double-click window @w2
+deskctl type "hello world" # Type text into focused window
+deskctl press enter # Press a key
+deskctl hotkey ctrl c # Send Ctrl+C
+deskctl hotkey ctrl shift t # Send Ctrl+Shift+T
+```
+
+### Mouse Control
+
+```bash
+deskctl mouse move 500 300 # Move cursor to coordinates
+deskctl mouse scroll 3 # Scroll down 3 units
+deskctl mouse scroll -3 # Scroll up 3 units
+deskctl mouse drag 100 100 500 500 # Drag from (100,100) to (500,500)
+```
+
+### Window Management
+
+```bash
+deskctl focus @w2 # Focus window by ref
+deskctl focus "firefox" # Focus window by name (substring match)
+deskctl close @w3 # Close window gracefully
+deskctl move-window @w1 100 200 # Move window to position
+deskctl resize-window @w1 800 600 # Resize window
+```
+
+### Utilities
+
+```bash
+deskctl get-screen-size # Screen resolution
+deskctl get-mouse-position # Current cursor position
+deskctl launch firefox # Launch an application
+deskctl launch code -- --new-window # Launch with arguments
+```
+
+### Daemon
+
+```bash
+deskctl daemon start # Start daemon manually
+deskctl daemon stop # Stop daemon
+deskctl daemon status # Check daemon status
+```
+
+## Global Options
+
+- `--json` : Output as structured JSON (all commands)
+- `--session NAME` : Session name for multiple daemon instances (default: "default")
+- `--socket PATH` : Custom Unix socket path
+
+## Window Refs
+
+After `snapshot` or `list-windows`, windows are assigned short refs:
+- `@w1` is the topmost (usually focused) window
+- `@w2`, `@w3`, etc. follow z-order (front to back)
+- Refs reset on each `snapshot` call
+- Use `--json` to see stable `xcb_id` for programmatic tracking
+
+## Example Agent Workflow
+
+```bash
+# 1. See what's on screen
+deskctl snapshot --annotate
+
+# 2. Focus the browser
+deskctl focus "firefox"
+
+# 3. Navigate to a URL
+deskctl hotkey ctrl l
+deskctl type "https://example.com"
+deskctl press enter
+
+# 4. Take a new snapshot to see the result
+deskctl snapshot
+```
+
+## Key Names for press/hotkey
+
+Modifiers: `ctrl`, `alt`, `shift`, `super`
+Navigation: `enter`, `tab`, `escape`, `backspace`, `delete`, `space`
+Arrows: `up`, `down`, `left`, `right`
+Page: `home`, `end`, `pageup`, `pagedown`
+Function: `f1` through `f12`
+Characters: any single character (e.g. `a`, `1`, `/`)
diff --git a/demo/index.html b/demo/index.html
deleted file mode 100644
index 70ac230..0000000
--- a/demo/index.html
+++ /dev/null
@@ -1,969 +0,0 @@
-
-
-
-
-
-deskctl - Desktop Control for AI Agents
-
-
-
-
-
-
deskctl
-
desktop control CLI for AI agents
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 📝
- 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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $950
- $800
- $650
-
-
-
-
-
-
-
-
-
-
-
Chrome - Google Docs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- NVDA 1Y
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Files
-
Yahoo Finance
-
Google Docs
-
-
-
-
-
-
-
-
-
AI agent controlling a live desktop via deskctl
-
↺ Replay
-
-
-
-
-
diff --git a/docs/releasing.md b/docs/releasing.md
deleted file mode 100644
index 849d661..0000000
--- a/docs/releasing.md
+++ /dev/null
@@ -1,110 +0,0 @@
-# Releasing deskctl
-
-This document covers the operator flow for shipping `deskctl` across:
-
-- GitHub Releases
-- crates.io
-- npm
-- the repo flake
-
-GitHub Releases are the canonical binary source. The npm package consumes those release assets instead of building a separate binary.
-
-## Package Names
-
-- crate: `deskctl`
-- npm package: `deskctl`
-- installed command: `deskctl`
-
-## Prerequisites
-
-Before the first live publish on each registry:
-
-- npm ownership for `deskctl`
-- crates.io ownership for `deskctl`
-- repository secrets:
- - `NPM_TOKEN`
- - `CARGO_REGISTRY_TOKEN`
-
-These are user-owned prerequisites. The repo can validate and automate the rest, but it cannot create registry ownership for you.
-
-## Normal Release Flow
-
-1. Merge release-ready changes to `main`.
-2. Let CI run:
- - validation
- - integration
- - distribution validation
- - release asset build
-3. Confirm the GitHub Release exists for the version tag and includes:
- - `deskctl-linux-x86_64`
- - `checksums.txt`
-4. Trigger the `Publish Registries` workflow with:
- - `tag`
- - `publish_npm`
- - `publish_crates`
-5. Confirm the publish summary for each channel.
-
-## What CI Validates
-
-The repository validates:
-
-- `cargo publish --dry-run --locked`
-- npm package metadata and packability
-- npm install smoke path on Linux using the packaged `deskctl` command
-- repo flake evaluation/build
-
-The repository release workflow:
-
-- builds the Linux release binary
-- publishes the canonical GitHub Release asset
-- uploads `checksums.txt`
-
-The registry publish jobs (npm and crates.io run in parallel):
-
-- 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
-
-Registry publishing is intentionally separate from release asset creation.
-
-If a partial failure happens:
-
-- GitHub Release assets remain the source of truth
-- rerun the `Publish Registries` workflow for the same tag
-- already-published channels are reported and skipped
-- remaining channels can still be published
-
-## Local Validation
-
-Run the distribution checks locally with:
-
-```bash
-make cargo-publish-dry-run
-make npm-package-check
-make nix-flake-check
-make dist-validate
-```
-
-Notes:
-
-- `make npm-package-check` does a runtime smoke test only on Linux
-- `make nix-flake-check` requires a local Nix installation
-- Docker remains a local Linux build convenience, not the canonical release path
-
-## Nix Boundary
-
-The repo-owned `flake.nix` is the supported Nix surface in this phase.
-
-In scope:
-
-- `nix run github:harivansh-afk/deskctl`
-- `nix profile install github:harivansh-afk/deskctl`
-- CI validation for the repo flake
-
-Out of scope for this phase:
-
-- `nixpkgs` upstreaming
-- extra distro packaging outside the repo
diff --git a/docs/runtime-contract.md b/docs/runtime-contract.md
deleted file mode 100644
index ee4727b..0000000
--- a/docs/runtime-contract.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# deskctl runtime contract
-
-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
diff --git a/flake.lock b/flake.lock
deleted file mode 100644
index f194334..0000000
--- a/flake.lock
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "nodes": {
- "flake-utils": {
- "inputs": {
- "systems": "systems"
- },
- "locked": {
- "lastModified": 1731533236,
- "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "nixpkgs": {
- "locked": {
- "lastModified": 1774386573,
- "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "flake-utils": "flake-utils",
- "nixpkgs": "nixpkgs"
- }
- },
- "systems": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/flake.nix b/flake.nix
deleted file mode 100644
index 1eafbaa..0000000
--- a/flake.nix
+++ /dev/null
@@ -1,77 +0,0 @@
-{
- description = "deskctl - Desktop control CLI for AI agents on Linux X11";
-
- inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
- flake-utils.url = "github:numtide/flake-utils";
- };
-
- outputs =
- { self, nixpkgs, flake-utils }:
- flake-utils.lib.eachDefaultSystem (
- system:
- let
- pkgs = import nixpkgs { inherit system; };
- lib = pkgs.lib;
- cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
-
- deskctl =
- pkgs.rustPlatform.buildRustPackage {
- pname = cargoToml.package.name;
- version = cargoToml.package.version;
- src = ./.;
- cargoLock.lockFile = ./Cargo.lock;
- nativeBuildInputs = [ pkgs.pkg-config ];
- buildInputs = lib.optionals pkgs.stdenv.isLinux [
- pkgs.libx11
- pkgs.libxtst
- ];
- doCheck = false;
-
- meta = with lib; {
- description = cargoToml.package.description;
- homepage = cargoToml.package.homepage;
- license = licenses.mit;
- mainProgram = "deskctl";
- platforms = platforms.linux;
- };
- };
- in
- {
- formatter = pkgs.nixfmt;
-
- packages = lib.optionalAttrs pkgs.stdenv.isLinux {
- inherit deskctl;
- default = deskctl;
- };
-
- apps = lib.optionalAttrs pkgs.stdenv.isLinux {
- default = flake-utils.lib.mkApp { drv = deskctl; };
- deskctl = flake-utils.lib.mkApp { drv = deskctl; };
- };
-
- checks = lib.optionalAttrs pkgs.stdenv.isLinux {
- build = deskctl;
- };
-
- devShells.default = pkgs.mkShell {
- packages =
- [
- pkgs.cargo
- pkgs.clippy
- pkgs.nodejs
- pkgs.nixfmt
- pkgs.pkg-config
- pkgs.pnpm
- pkgs.rustc
- pkgs.rustfmt
- ]
- ++ lib.optionals pkgs.stdenv.isLinux [
- pkgs.libx11
- pkgs.libxtst
- pkgs.xorg.xorgserver
- ];
- };
- }
- );
-}
diff --git a/npm/deskctl/README.md b/npm/deskctl/README.md
deleted file mode 100644
index 81f07f4..0000000
--- a/npm/deskctl/README.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# deskctl
-
-`deskctl` installs the command for Linux X11 systems.
-
-## Install
-
-```bash
-npm install -g deskctl
-```
-
-After install, run:
-
-```bash
-deskctl --help
-```
-
-To upgrade version:
-
-```bash
-deskctl upgrade
-```
-
-For non-interactive use:
-
-```bash
-deskctl upgrade --yes
-```
-
-One-shot usage is also supported:
-
-```bash
-npx deskctl --help
-```
-
-## Runtime Support
-
-- Linux
-- X11 session
-- currently packaged release asset: `linux-x64`
-
-`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:
-
-```bash
-cargo install deskctl
-```
diff --git a/npm/deskctl/bin/deskctl.js b/npm/deskctl/bin/deskctl.js
deleted file mode 100644
index b8514cf..0000000
--- a/npm/deskctl/bin/deskctl.js
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env node
-
-const fs = require("node:fs");
-const { spawn } = require("node:child_process");
-
-const { readPackageJson, releaseTag, supportedTarget, vendorBinaryPath } = require("../scripts/support");
-
-function main() {
- const pkg = readPackageJson();
- const target = supportedTarget();
- const binaryPath = vendorBinaryPath(target);
-
- if (!fs.existsSync(binaryPath)) {
- console.error(
- [
- "deskctl binary is missing from the npm package install.",
- `Expected: ${binaryPath}`,
- `Package version: ${pkg.version}`,
- `Release tag: ${releaseTag(pkg)}`,
- "Try reinstalling deskctl or check that your target is supported."
- ].join("\n")
- );
- process.exit(1);
- }
-
- const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
- child.on("exit", (code, signal) => {
- if (signal) {
- process.kill(process.pid, signal);
- return;
- }
- process.exit(code ?? 1);
- });
-}
-
-main();
diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json
deleted file mode 100644
index c676924..0000000
--- a/npm/deskctl/package.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "name": "deskctl",
- "version": "0.1.14",
- "description": "Installable deskctl package for Linux X11 agents",
- "license": "MIT",
- "homepage": "https://github.com/harivansh-afk/deskctl",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/harivansh-afk/deskctl.git"
- },
- "bugs": {
- "url": "https://github.com/harivansh-afk/deskctl/issues"
- },
- "engines": {
- "node": ">=18"
- },
- "bin": {
- "deskctl": "bin/deskctl.js"
- },
- "files": [
- "README.md",
- "bin",
- "scripts"
- ],
- "scripts": {
- "postinstall": "node scripts/postinstall.js",
- "validate": "node scripts/validate-package.js"
- },
- "keywords": [
- "deskctl",
- "x11",
- "desktop",
- "automation",
- "cli"
- ]
-}
diff --git a/npm/deskctl/scripts/postinstall.js b/npm/deskctl/scripts/postinstall.js
deleted file mode 100644
index 1f43ad0..0000000
--- a/npm/deskctl/scripts/postinstall.js
+++ /dev/null
@@ -1,49 +0,0 @@
-const fs = require("node:fs");
-
-const {
- checksumsUrl,
- checksumForAsset,
- download,
- ensureVendorDir,
- installLocalBinary,
- readPackageJson,
- releaseAssetUrl,
- releaseTag,
- sha256,
- supportedTarget,
- vendorBinaryPath
-} = require("./support");
-
-async function main() {
- const pkg = readPackageJson();
- const target = supportedTarget();
- const targetPath = vendorBinaryPath(target);
-
- ensureVendorDir();
-
- if (process.env.DESKCTL_BINARY_PATH) {
- installLocalBinary(process.env.DESKCTL_BINARY_PATH, targetPath);
- return;
- }
-
- const tag = releaseTag(pkg);
- const assetUrl = releaseAssetUrl(tag, target.assetName);
- const checksumText = (await download(checksumsUrl(tag))).toString("utf8");
- const expectedSha = checksumForAsset(checksumText, target.assetName);
- const asset = await download(assetUrl);
- const actualSha = sha256(asset);
-
- if (actualSha !== expectedSha) {
- throw new Error(
- `Checksum mismatch for ${target.assetName}. Expected ${expectedSha}, got ${actualSha}.`
- );
- }
-
- fs.writeFileSync(targetPath, asset);
- fs.chmodSync(targetPath, 0o755);
-}
-
-main().catch((error) => {
- console.error(`deskctl install failed: ${error.message}`);
- process.exit(1);
-});
diff --git a/npm/deskctl/scripts/support.js b/npm/deskctl/scripts/support.js
deleted file mode 100644
index 1fd0d47..0000000
--- a/npm/deskctl/scripts/support.js
+++ /dev/null
@@ -1,120 +0,0 @@
-const crypto = require("node:crypto");
-const fs = require("node:fs");
-const path = require("node:path");
-const https = require("node:https");
-
-const PACKAGE_ROOT = path.resolve(__dirname, "..");
-const VENDOR_DIR = path.join(PACKAGE_ROOT, "vendor");
-const PACKAGE_JSON = path.join(PACKAGE_ROOT, "package.json");
-
-function readPackageJson() {
- return JSON.parse(fs.readFileSync(PACKAGE_JSON, "utf8"));
-}
-
-function releaseTag(pkg) {
- return process.env.DESKCTL_RELEASE_TAG || `v${pkg.version}`;
-}
-
-function supportedTarget(platform = process.platform, arch = process.arch) {
- if (platform === "linux" && arch === "x64") {
- return {
- platform,
- arch,
- assetName: "deskctl-linux-x86_64",
- binaryName: "deskctl-linux-x86_64"
- };
- }
-
- throw new Error(
- `deskctl currently supports linux-x64 only. Received ${platform}-${arch}.`
- );
-}
-
-function vendorBinaryPath(target) {
- return path.join(VENDOR_DIR, target.binaryName);
-}
-
-function releaseBaseUrl(tag) {
- return (
- process.env.DESKCTL_RELEASE_BASE_URL ||
- `https://github.com/harivansh-afk/deskctl/releases/download/${tag}`
- );
-}
-
-function releaseAssetUrl(tag, assetName) {
- return process.env.DESKCTL_DOWNLOAD_URL || `${releaseBaseUrl(tag)}/${assetName}`;
-}
-
-function checksumsUrl(tag) {
- return `${releaseBaseUrl(tag)}/checksums.txt`;
-}
-
-function ensureVendorDir() {
- fs.mkdirSync(VENDOR_DIR, { recursive: true });
-}
-
-function checksumForAsset(contents, assetName) {
- const line = contents
- .split("\n")
- .map((value) => value.trim())
- .find((value) => value.endsWith(` ${assetName}`) || value.endsWith(` *${assetName}`));
-
- if (!line) {
- throw new Error(`Could not find checksum entry for ${assetName}.`);
- }
-
- return line.split(/\s+/)[0];
-}
-
-function sha256(buffer) {
- return crypto.createHash("sha256").update(buffer).digest("hex");
-}
-
-function download(url) {
- return new Promise((resolve, reject) => {
- https
- .get(url, (response) => {
- if (
- response.statusCode &&
- response.statusCode >= 300 &&
- response.statusCode < 400 &&
- response.headers.location
- ) {
- response.resume();
- resolve(download(response.headers.location));
- return;
- }
-
- if (response.statusCode !== 200) {
- reject(new Error(`Download failed for ${url}: HTTP ${response.statusCode}`));
- return;
- }
-
- const chunks = [];
- response.on("data", (chunk) => chunks.push(chunk));
- response.on("end", () => resolve(Buffer.concat(chunks)));
- })
- .on("error", reject);
- });
-}
-
-function installLocalBinary(sourcePath, targetPath) {
- fs.copyFileSync(sourcePath, targetPath);
- fs.chmodSync(targetPath, 0o755);
-}
-
-module.exports = {
- PACKAGE_ROOT,
- VENDOR_DIR,
- checksumsUrl,
- checksumForAsset,
- download,
- ensureVendorDir,
- installLocalBinary,
- readPackageJson,
- releaseAssetUrl,
- releaseTag,
- sha256,
- supportedTarget,
- vendorBinaryPath
-};
diff --git a/npm/deskctl/scripts/validate-package.js b/npm/deskctl/scripts/validate-package.js
deleted file mode 100644
index 450fd6c..0000000
--- a/npm/deskctl/scripts/validate-package.js
+++ /dev/null
@@ -1,40 +0,0 @@
-const fs = require("node:fs");
-const path = require("node:path");
-
-const { readPackageJson, supportedTarget, vendorBinaryPath } = require("./support");
-
-function readCargoVersion() {
- const cargoToml = fs.readFileSync(
- path.resolve(__dirname, "..", "..", "..", "Cargo.toml"),
- "utf8"
- );
- const match = cargoToml.match(/^version = "([^"]+)"/m);
- if (!match) {
- throw new Error("Could not determine Cargo.toml version.");
- }
- return match[1];
-}
-
-function main() {
- const pkg = readPackageJson();
- const cargoVersion = readCargoVersion();
-
- if (pkg.version !== cargoVersion) {
- throw new Error(
- `Version mismatch: npm package is ${pkg.version}, Cargo.toml is ${cargoVersion}.`
- );
- }
-
- if (pkg.bin?.deskctl !== "bin/deskctl.js") {
- 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", "vendor"))) {
- throw new Error("Vendor binary directory resolved unexpectedly.");
- }
-}
-
-main();
diff --git a/site/.gitignore b/site/.gitignore
deleted file mode 100644
index 71d18a2..0000000
--- a/site/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-node_modules/
-dist/
-.astro/
-.vercel/
diff --git a/site/.prettierrc b/site/.prettierrc
deleted file mode 100644
index d87faa5..0000000
--- a/site/.prettierrc
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "plugins": ["prettier-plugin-astro"],
- "overrides": [
- {
- "files": "*.astro",
- "options": {
- "parser": "astro"
- }
- }
- ]
-}
diff --git a/site/astro.config.mjs b/site/astro.config.mjs
deleted file mode 100644
index c1ae52c..0000000
--- a/site/astro.config.mjs
+++ /dev/null
@@ -1,22 +0,0 @@
-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
deleted file mode 100644
index 7842c9f..0000000
--- a/site/package.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "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
deleted file mode 100644
index bad2591..0000000
--- a/site/pnpm-lock.yaml
+++ /dev/null
@@ -1,5105 +0,0 @@
-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
deleted file mode 100644
index 1c3771a..0000000
Binary files a/site/public/favicon.png and /dev/null differ
diff --git a/site/public/favicon.svg b/site/public/favicon.svg
deleted file mode 100644
index 400dec5..0000000
--- a/site/public/favicon.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/site/src/layouts/DocLayout.astro b/site/src/layouts/DocLayout.astro
deleted file mode 100644
index afc8648..0000000
--- a/site/src/layouts/DocLayout.astro
+++ /dev/null
@@ -1,115 +0,0 @@
----
-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 && (
-
-
- deskctl
-
- /
- {title}
-
- )
- }
-
- {
- toc && (
-
-
- {h2s.map((h) => (
-
-
-
- ))}
-
-
- )
- }
-
-
-
-
-
-
-
-
diff --git a/site/src/pages/commands.mdx b/site/src/pages/commands.mdx
deleted file mode 100644
index 0696558..0000000
--- a/site/src/pages/commands.mdx
+++ /dev/null
@@ -1,114 +0,0 @@
----
-layout: ../layouts/DocLayout.astro
-title: Commands
-toc: true
----
-
-# Commands
-
-The public CLI is intentionally small. Most workflows boil down to grouped
-reads, grouped waits, selector-driven actions, and a few input primitives.
-
-## Observe and inspect
-
-```sh
-deskctl doctor
-deskctl upgrade
-deskctl snapshot
-deskctl snapshot --annotate
-deskctl list-windows
-deskctl screenshot
-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
-```
-
-`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.
-
-## Wait for state transitions
-
-```sh
-deskctl wait window --selector 'title=Chromium' --timeout 10
-deskctl wait focus --selector 'id=win3' --timeout 5
-deskctl --json wait window --selector 'class=chromium' --poll-ms 100
-```
-
-Wait commands return the matched window payload on success. In `--json` mode,
-timeouts and selector failures expose structured `kind` values.
-
-## Act on windows
-
-```sh
-deskctl launch chromium
-deskctl focus @w1
-deskctl focus 'title=Chromium'
-deskctl click @w1
-deskctl click 960,540
-deskctl dblclick @w2
-deskctl close @w3
-deskctl move-window @w1 100 120
-deskctl resize-window @w1 1280 720
-```
-
-Selector-driven actions accept refs, explicit selector modes, or absolute
-coordinates where appropriate.
-
-## Keyboard and mouse input
-
-```sh
-deskctl type "hello world"
-deskctl press enter
-deskctl hotkey ctrl shift t
-deskctl mouse move 100 200
-deskctl mouse scroll 3
-deskctl mouse scroll 3 --axis horizontal
-deskctl mouse drag 100 200 500 600
-```
-
-Supported key names include `enter`, `tab`, `escape`, `backspace`, `delete`,
-`space`, arrow keys, paging keys, `f1` through `f12`, and any single
-character.
-
-## Selectors
-
-Prefer explicit selectors when the target matters. They are clearer in logs,
-more deterministic for automation, and easier to retry safely.
-
-```sh
-ref=w1
-id=win1
-title=Chromium
-class=chromium
-focused
-```
-
-Legacy shorthand is still supported:
-
-```sh
-@w1
-w1
-win1
-```
-
-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
-
-| Flag | Env | Description |
-| ------------------ | ---------------- | ------------------------------------------------------ |
-| `--json` | | Output as JSON |
-| `--socket ` | `DESKCTL_SOCKET` | Path to daemon Unix socket |
-| `--session ` | | Session name for multiple daemons (default: `default`) |
-
-`deskctl` manages the daemon automatically. Most users never need to think
-about it beyond `--session` and `--socket`.
diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro
deleted file mode 100644
index 478c7a2..0000000
--- a/site/src/pages/index.astro
+++ /dev/null
@@ -1,53 +0,0 @@
----
-import DocLayout from "../layouts/DocLayout.astro";
----
-
-
-
- deskctl
-
-
-
- non-interactive desktop control cli for AI agents
-
-
- A thin X11 control primitive for agent loops: diagnose the runtime, observe
- the desktop, wait for state transitions, act deterministically, then verify.
-
-
- Start
-
-
-
- Reference
-
-
-
- Links
-
-
-
diff --git a/site/src/pages/installation.mdx b/site/src/pages/installation.mdx
deleted file mode 100644
index e35f4eb..0000000
--- a/site/src/pages/installation.mdx
+++ /dev/null
@@ -1,76 +0,0 @@
----
-layout: ../layouts/DocLayout.astro
-title: Installation
-toc: true
----
-
-# Installation
-
-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.
-
-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
-
-### Nix
-
-```sh
-nix run github:harivansh-afk/deskctl -- --help
-nix profile install github:harivansh-afk/deskctl
-```
-
-### Rust
-
-```sh
-git clone https://github.com/harivansh-afk/deskctl
-cd deskctl
-cargo build
-```
-
-Source builds on Linux require:
-
-- Rust 1.75+
-- `pkg-config`
-- X11 development libraries such as `libx11-dev` and `libxtst-dev`
-
-## Runtime requirements
-
-- Linux with an active X11 session
-- `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`
-
-The binary itself only depends on the standard Linux glibc runtime.
-
-## 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
deleted file mode 100644
index 4cc0e25..0000000
--- a/site/src/pages/quick-start.mdx
+++ /dev/null
@@ -1,105 +0,0 @@
----
-layout: ../layouts/DocLayout.astro
-title: Quick start
-toc: true
----
-
-# Quick start
-
-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
-```
-
-Run `deskctl doctor` first. It checks X11 connectivity, basic enumeration,
-screenshot viability, and socket health before you start driving the desktop.
-
-## 2. Observe the desktop
-
-```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.
-
-## 3. Pick selectors that stay readable
-
-Prefer explicit selectors when you need deterministic targeting:
-
-```sh
-ref=w1
-id=win1
-title=Chromium
-class=chromium
-focused
-```
-
-Legacy refs such as `@w1` still work after `snapshot` or `list-windows`. Bare
-strings like `chromium` are fuzzy matches and now fail on ambiguity.
-
-## 4. Wait, act, verify
-
-The core loop is:
-
-```sh
-# observe
-deskctl snapshot --annotate
-
-# wait
-deskctl wait window --selector 'title=Chromium' --timeout 10
-
-# act
-deskctl focus 'title=Chromium'
-deskctl hotkey ctrl l
-deskctl type "https://example.com"
-deskctl press enter
-
-# verify
-deskctl wait focus --selector 'title=Chromium' --timeout 5
-deskctl snapshot
-```
-
-The wait commands return the matched window payload on success, so they compose
-cleanly into the next action.
-
-## 5. Use `--json` when parsing matters
-
-Every command supports `--json` and uses the same top-level envelope:
-
-```json
-{
- "success": true,
- "data": {
- "screenshot": "/tmp/deskctl-1234567890.png",
- "windows": [
- {
- "ref_id": "w1",
- "window_id": "win1",
- "title": "Chromium",
- "app_name": "chromium",
- "x": 0,
- "y": 0,
- "width": 1920,
- "height": 1080,
- "focused": true,
- "minimized": false
- }
- ]
- }
-}
-```
-
-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 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
deleted file mode 100644
index e33e999..0000000
--- a/site/src/pages/runtime-contract.mdx
+++ /dev/null
@@ -1,177 +0,0 @@
----
-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.
-
-## Stable top-level 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 payload
-
-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
deleted file mode 100644
index e05552e..0000000
--- a/site/src/styles/base.css
+++ /dev/null
@@ -1,303 +0,0 @@
-@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")
- 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: "Manrope", sans-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;
-}
-
-.lede {
- font-size: 1.05rem;
- max-width: 42rem;
-}
-
-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;
-}
-
-img {
- max-width: 100%;
-}
-
-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;
- }
-}
-
-.breadcrumbs {
- max-width: 50rem;
- margin: 0 auto;
- padding: 1.5rem clamp(1.25rem, 5vw, 3rem) 0;
- font-size: 0.9rem;
-}
-
-.breadcrumbs a {
- color: inherit;
- text-decoration: none;
- opacity: 0.6;
- transition: opacity 0.15s;
-}
-
-.breadcrumbs a:hover {
- opacity: 1;
-}
-
-.breadcrumbs .title {
- font-weight: 500;
- opacity: 1;
-}
-
-.breadcrumbs .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
deleted file mode 100644
index 913b2ce..0000000
--- a/site/src/themes.mjs
+++ /dev/null
@@ -1,59 +0,0 @@
-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
deleted file mode 100644
index 7648092..0000000
--- a/site/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "astro/tsconfigs/strict",
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "@layouts/*": ["src/layouts/*"],
- "@components/*": ["src/components/*"]
- }
- }
-}
diff --git a/skills/deskctl/SKILL.md b/skills/deskctl/SKILL.md
deleted file mode 100644
index c79ca21..0000000
--- a/skills/deskctl/SKILL.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-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:*), Bash(npm:*), Bash(which:*), Bash(printenv:*), Bash(echo:*)
----
-
-# deskctl
-
-Non-interactive desktop control CLI for Linux X11 agents.
-
-All output follows the runtime contract defined in [references/runtime-contract.md](references/runtime-contract.md). Every command returns a stable JSON envelope when called with `--json`. Use `--json` whenever you need to parse output programmatically.
-
-## Quick start
-
-```bash
-npm install -g deskctl
-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**.
-
-```bash
-deskctl snapshot --annotate # observe
-deskctl wait window --selector 'title=Chromium' --timeout 10 # wait
-deskctl click 'title=Chromium' # act
-deskctl snapshot # verify
-```
-
-See [workflows/observe-act.sh](workflows/observe-act.sh) for a reusable script. See [workflows/poll-condition.sh](workflows/poll-condition.sh) for polling loops.
-
-## Selectors
-
-```bash
-ref=w1 # snapshot ref (short-lived)
-id=win1 # stable window ID (session-scoped)
-title=Chromium # match by title
-class=chromium # match by WM class
-focused # currently focused window
-```
-
-Bare strings like `chromium` do fuzzy matching but fail on ambiguity. Prefer explicit selectors.
-
-## References
-
-- [references/runtime-contract.md](references/runtime-contract.md) - output contract, stable fields, error kinds
-- [references/commands.md](references/commands.md) - all available commands
-
-## Workflows
-
-- [workflows/observe-act.sh](workflows/observe-act.sh) - main observe-act loop
-- [workflows/poll-condition.sh](workflows/poll-condition.sh) - poll for a condition on screen
diff --git a/skills/deskctl/agents/openai.yaml b/skills/deskctl/agents/openai.yaml
deleted file mode 100644
index 8a5ca13..0000000
--- a/skills/deskctl/agents/openai.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-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/commands.md b/skills/deskctl/references/commands.md
deleted file mode 100644
index df69350..0000000
--- a/skills/deskctl/references/commands.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# deskctl commands
-
-All commands support `--json` for machine-parseable output following the
-runtime contract.
-
-## Observe
-
-```bash
-deskctl doctor
-deskctl upgrade
-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
-
-```bash
-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
-`kind` values in `--json` mode.
-
-## Selectors
-
-```bash
-ref=w1
-id=win1
-title=Chromium
-class=chromium
-focused
-```
-
-Legacy shorthand: `@w1`, `w1`, `win1`. Bare strings do fuzzy matching but fail
-on ambiguity.
-
-## Act
-
-```bash
-deskctl focus 'class=chromium'
-deskctl click @w1
-deskctl dblclick @w2
-deskctl type "hello world"
-deskctl press enter
-deskctl hotkey ctrl shift t
-deskctl mouse move 500 300
-deskctl mouse scroll 3
-deskctl mouse scroll 3 --axis horizontal
-deskctl mouse drag 100 100 500 500
-deskctl move-window @w1 100 120
-deskctl resize-window @w1 1280 720
-deskctl close @w3
-deskctl launch chromium
-```
-
-The daemon starts automatically on first command. In normal usage you should
-not need to manage it directly.
diff --git a/skills/deskctl/references/runtime-contract.md b/skills/deskctl/references/runtime-contract.md
deleted file mode 100644
index 6efd2bc..0000000
--- a/skills/deskctl/references/runtime-contract.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# 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
diff --git a/skills/deskctl/workflows/observe-act.sh b/skills/deskctl/workflows/observe-act.sh
deleted file mode 100755
index 8c3abc2..0000000
--- a/skills/deskctl/workflows/observe-act.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env bash
-# observe-act.sh - main desktop interaction loop
-# usage: ./observe-act.sh [action] [action-args...]
-# example: ./observe-act.sh 'title=Chromium' click
-# example: ./observe-act.sh 'class=terminal' type "ls -la"
-set -euo pipefail
-
-SELECTOR="${1:?usage: observe-act.sh [action] [action-args...]}"
-ACTION="${2:-click}"
-shift 2 2>/dev/null || true
-
-# 1. observe - snapshot the desktop, get current state
-echo "--- observe ---"
-deskctl snapshot --annotate --json | head -1
-deskctl get active-window
-
-# 2. wait - ensure target exists
-echo "--- wait ---"
-deskctl wait window --selector "$SELECTOR" --timeout 10
-
-# 3. act - perform the action on the target
-echo "--- act ---"
-case "$ACTION" in
- click) deskctl click "$SELECTOR" ;;
- dblclick) deskctl dblclick "$SELECTOR" ;;
- focus) deskctl focus "$SELECTOR" ;;
- type) deskctl focus "$SELECTOR" && deskctl type "$@" ;;
- press) deskctl focus "$SELECTOR" && deskctl press "$@" ;;
- hotkey) deskctl focus "$SELECTOR" && deskctl hotkey "$@" ;;
- close) deskctl close "$SELECTOR" ;;
- *) echo "unknown action: $ACTION"; exit 1 ;;
-esac
-
-# 4. verify - snapshot again to confirm result
-echo "--- verify ---"
-sleep 0.5
-deskctl snapshot --json | head -1
diff --git a/skills/deskctl/workflows/poll-condition.sh b/skills/deskctl/workflows/poll-condition.sh
deleted file mode 100755
index e173bf5..0000000
--- a/skills/deskctl/workflows/poll-condition.sh
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env bash
-# poll-condition.sh - poll the desktop until a condition is met
-# usage: ./poll-condition.sh [interval-seconds] [max-attempts]
-# example: ./poll-condition.sh "Tickets Available" 5 60
-# example: ./poll-condition.sh "Order Confirmed" 3 20
-# example: ./poll-condition.sh "Download Complete" 10 30
-#
-# checks window titles for the match string every N seconds.
-# exits 0 when found, exits 1 after max attempts.
-set -euo pipefail
-
-MATCH="${1:?usage: poll-condition.sh [interval] [max-attempts]}"
-INTERVAL="${2:-5}"
-MAX="${3:-60}"
-
-attempt=0
-while [ "$attempt" -lt "$MAX" ]; do
- attempt=$((attempt + 1))
-
- # snapshot and check window titles
- windows=$(deskctl list-windows --json 2>/dev/null || echo '{"success":false}')
- if echo "$windows" | grep -qi "$MATCH"; then
- echo "FOUND: '$MATCH' detected on attempt $attempt"
- deskctl snapshot --annotate
- exit 0
- fi
-
- # also check screenshot text via active window title
- active=$(deskctl get active-window --json 2>/dev/null || echo '{}')
- if echo "$active" | grep -qi "$MATCH"; then
- echo "FOUND: '$MATCH' in active window on attempt $attempt"
- deskctl snapshot --annotate
- exit 0
- fi
-
- echo "attempt $attempt/$MAX - '$MATCH' not found, waiting ${INTERVAL}s..."
- sleep "$INTERVAL"
-done
-
-echo "NOT FOUND: '$MATCH' after $MAX attempts"
-deskctl snapshot --annotate
-exit 1
diff --git a/src/backend/annotate.rs b/src/backend/annotate.rs
index 4a479c7..d4f4fed 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
+// Embedded font - DejaVu Sans Mono for guaranteed availability
const FONT_BYTES: &[u8] = include_bytes!("../../assets/DejaVuSansMono.ttf");
const COLORS: &[Rgba] = &[
diff --git a/src/backend/mod.rs b/src/backend/mod.rs
index ea4df7f..4d1767a 100644
--- a/src/backend/mod.rs
+++ b/src/backend/mod.rs
@@ -1,60 +1,25 @@
pub mod annotate;
pub mod x11;
+use crate::core::types::Snapshot;
use anyhow::Result;
-use image::RgbaImage;
-
-#[derive(Debug, Clone)]
-pub struct BackendWindow {
- pub native_id: u32,
- pub title: String,
- pub app_name: String,
- pub x: i32,
- pub y: i32,
- pub width: u32,
- pub height: u32,
- pub focused: bool,
- pub minimized: bool,
-}
-
-#[derive(Debug, Clone)]
-pub struct BackendMonitor {
- pub name: String,
- pub x: i32,
- pub y: i32,
- pub width: u32,
- pub height: u32,
- pub width_mm: u32,
- pub height_mm: u32,
- pub primary: bool,
- pub automatic: bool,
-}
#[allow(dead_code)]
pub trait DesktopBackend: Send {
- /// Collect z-ordered windows for read-only queries and targeting.
- fn list_windows(&mut self) -> Result>;
+ /// Capture a screenshot and return a z-ordered window tree with @wN refs.
+ fn snapshot(&mut self, annotate: bool) -> Result;
- /// Get the currently focused window, if one is known.
- fn active_window(&mut self) -> Result>;
-
- /// Collect monitor geometry and metadata.
- fn list_monitors(&self) -> Result>;
-
- /// Capture the current desktop image without writing it to disk.
- fn capture_screenshot(&mut self) -> Result;
-
- /// Focus a window by its backend-native window handle.
- fn focus_window(&mut self, native_id: u32) -> Result<()>;
+ /// Focus a window by its X11 window ID.
+ fn focus_window(&mut self, xcb_id: u32) -> Result<()>;
/// Move a window to absolute coordinates.
- fn move_window(&mut self, native_id: u32, x: i32, y: i32) -> Result<()>;
+ fn move_window(&mut self, xcb_id: u32, x: i32, y: i32) -> Result<()>;
/// Resize a window.
- fn resize_window(&mut self, native_id: u32, w: u32, h: u32) -> Result<()>;
+ fn resize_window(&mut self, xcb_id: u32, w: u32, h: u32) -> Result<()>;
/// Close a window gracefully.
- fn close_window(&mut self, native_id: u32) -> Result<()>;
+ fn close_window(&mut self, xcb_id: u32) -> Result<()>;
/// Click at absolute coordinates.
fn click(&mut self, x: i32, y: i32) -> Result<()>;
@@ -86,9 +51,9 @@ pub trait DesktopBackend: Send {
/// Get the current mouse position.
fn mouse_position(&self) -> Result<(i32, i32)>;
+ /// Take a screenshot and save to a path (no window tree).
+ fn screenshot(&mut self, path: &str, annotate: bool) -> Result;
+
/// Launch an application.
fn launch(&self, command: &str, args: &[String]) -> Result;
-
- /// Human-readable backend name for diagnostics and runtime queries.
- fn backend_name(&self) -> &'static str;
}
diff --git a/src/backend/x11.rs b/src/backend/x11.rs
index 7b1b396..9502281 100644
--- a/src/backend/x11.rs
+++ b/src/backend/x11.rs
@@ -2,7 +2,6 @@ use anyhow::{Context, Result};
use enigo::{Axis, Button, Coordinate, Direction, Enigo, Key, Keyboard, Mouse, Settings};
use image::RgbaImage;
use x11rb::connection::Connection;
-use x11rb::protocol::randr::ConnectionExt as RandrConnectionExt;
use x11rb::protocol::xproto::{
Atom, AtomEnum, ClientMessageData, ClientMessageEvent, ConfigureWindowAux,
ConnectionExt as XprotoConnectionExt, EventMask, GetPropertyReply, ImageFormat, ImageOrder,
@@ -10,7 +9,8 @@ use x11rb::protocol::xproto::{
};
use x11rb::rust_connection::RustConnection;
-use crate::backend::{BackendMonitor, BackendWindow};
+use super::annotate::annotate_screenshot;
+use crate::core::types::{Snapshot, WindowInfo};
struct Atoms {
client_list_stacking: Atom,
@@ -71,9 +71,10 @@ impl X11Backend {
Ok(windows)
}
- fn collect_window_infos(&self) -> Result> {
+ fn collect_window_infos(&self) -> Result> {
let active_window = self.active_window()?;
let mut window_infos = Vec::new();
+ let mut ref_counter = 1usize;
for window in self.stacked_windows()? {
let title = self.window_title(window).unwrap_or_default();
@@ -88,8 +89,9 @@ impl X11Backend {
};
let minimized = self.window_is_minimized(window).unwrap_or(false);
- window_infos.push(BackendWindow {
- native_id: window,
+ window_infos.push(WindowInfo {
+ ref_id: format!("w{ref_counter}"),
+ xcb_id: window,
title,
app_name,
x,
@@ -99,79 +101,12 @@ impl X11Backend {
focused: active_window == Some(window),
minimized,
});
+ ref_counter += 1;
}
Ok(window_infos)
}
- fn active_window_info(&self) -> Result> {
- let Some(active_window) = self.active_window()? else {
- return Ok(None);
- };
-
- let title = self.window_title(active_window).unwrap_or_default();
- let app_name = self.window_app_name(active_window).unwrap_or_default();
- if title.is_empty() && app_name.is_empty() {
- return Ok(None);
- }
-
- let (x, y, width, height) = self.window_geometry(active_window)?;
- let minimized = self.window_is_minimized(active_window).unwrap_or(false);
- Ok(Some(BackendWindow {
- native_id: active_window,
- title,
- app_name,
- x,
- y,
- width,
- height,
- focused: true,
- minimized,
- }))
- }
-
- fn collect_monitors(&self) -> Result> {
- let reply = self
- .conn
- .randr_get_monitors(self.root, true)?
- .reply()
- .context("Failed to query RANDR monitors")?;
-
- let mut monitors = Vec::with_capacity(reply.monitors.len());
- for (index, monitor) in reply.monitors.into_iter().enumerate() {
- monitors.push(BackendMonitor {
- name: self
- .atom_name(monitor.name)
- .unwrap_or_else(|_| format!("monitor{}", index + 1)),
- x: i32::from(monitor.x),
- y: i32::from(monitor.y),
- width: u32::from(monitor.width),
- height: u32::from(monitor.height),
- width_mm: monitor.width_in_millimeters,
- height_mm: monitor.height_in_millimeters,
- primary: monitor.primary,
- automatic: monitor.automatic,
- });
- }
-
- if monitors.is_empty() {
- let (width, height) = self.root_geometry()?;
- monitors.push(BackendMonitor {
- name: "screen".to_string(),
- x: 0,
- y: 0,
- width,
- height,
- width_mm: 0,
- height_mm: 0,
- primary: true,
- automatic: true,
- });
- }
-
- Ok(monitors)
- }
-
fn capture_root_image(&self) -> Result {
let (width, height) = self.root_geometry()?;
let reply = self
@@ -293,50 +228,35 @@ impl X11Backend {
.reply()
.with_context(|| format!("Failed to read property {property} from window {window}"))
}
-
- fn atom_name(&self, atom: Atom) -> Result {
- self.conn
- .get_atom_name(atom)?
- .reply()
- .map(|reply| String::from_utf8_lossy(&reply.name).to_string())
- .with_context(|| format!("Failed to read atom name for {atom}"))
- }
}
impl super::DesktopBackend for X11Backend {
- fn list_windows(&mut self) -> Result> {
- self.collect_window_infos()
- }
+ fn snapshot(&mut self, annotate: bool) -> Result {
+ let window_infos = self.collect_window_infos()?;
+ let mut image = self.capture_root_image()?;
- fn active_window(&mut self) -> Result> {
- self.active_window_info()
- }
-
- fn list_monitors(&self) -> Result> {
- match self.collect_monitors() {
- Ok(monitors) => Ok(monitors),
- Err(_) => {
- let (width, height) = self.root_geometry()?;
- Ok(vec![BackendMonitor {
- name: "screen".to_string(),
- x: 0,
- y: 0,
- width,
- height,
- width_mm: 0,
- height_mm: 0,
- primary: true,
- automatic: true,
- }])
- }
+ // Annotate if requested - draw bounding boxes and @wN labels
+ if annotate {
+ annotate_screenshot(&mut image, &window_infos);
}
+
+ // Save screenshot
+ let timestamp = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_millis();
+ let screenshot_path = format!("/tmp/deskctl-{timestamp}.png");
+ image
+ .save(&screenshot_path)
+ .context("Failed to save screenshot")?;
+
+ Ok(Snapshot {
+ screenshot: screenshot_path,
+ windows: window_infos,
+ })
}
- fn capture_screenshot(&mut self) -> Result {
- self.capture_root_image()
- }
-
- fn focus_window(&mut self, native_id: u32) -> Result<()> {
+ fn focus_window(&mut self, xcb_id: u32) -> Result<()> {
// Use _NET_ACTIVE_WINDOW client message (avoids focus-stealing prevention)
let net_active = self
.conn
@@ -349,7 +269,7 @@ impl super::DesktopBackend for X11Backend {
response_type: x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT,
format: 32,
sequence: 0,
- window: native_id,
+ window: xcb_id,
type_: net_active,
data: ClientMessageData::from([
2u32, 0, 0, 0, 0, // source=2 (pager), timestamp=0, currently_active=0
@@ -368,25 +288,25 @@ impl super::DesktopBackend for X11Backend {
Ok(())
}
- fn move_window(&mut self, native_id: u32, x: i32, y: i32) -> Result<()> {
+ fn move_window(&mut self, xcb_id: u32, x: i32, y: i32) -> Result<()> {
self.conn
- .configure_window(native_id, &ConfigureWindowAux::new().x(x).y(y))?;
+ .configure_window(xcb_id, &ConfigureWindowAux::new().x(x).y(y))?;
self.conn
.flush()
.context("Failed to flush X11 connection")?;
Ok(())
}
- fn resize_window(&mut self, native_id: u32, w: u32, h: u32) -> Result<()> {
+ fn resize_window(&mut self, xcb_id: u32, w: u32, h: u32) -> Result<()> {
self.conn
- .configure_window(native_id, &ConfigureWindowAux::new().width(w).height(h))?;
+ .configure_window(xcb_id, &ConfigureWindowAux::new().width(w).height(h))?;
self.conn
.flush()
.context("Failed to flush X11 connection")?;
Ok(())
}
- fn close_window(&mut self, native_id: u32) -> Result<()> {
+ fn close_window(&mut self, xcb_id: u32) -> Result<()> {
// Use _NET_CLOSE_WINDOW for graceful close (respects WM protocols)
let net_close = self
.conn
@@ -399,7 +319,7 @@ impl super::DesktopBackend for X11Backend {
response_type: x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT,
format: 32,
sequence: 0,
- window: native_id,
+ window: xcb_id,
type_: net_close,
data: ClientMessageData::from([
0u32, 2, 0, 0, 0, // timestamp=0, source=2 (pager)
@@ -543,6 +463,18 @@ impl super::DesktopBackend for X11Backend {
Ok((reply.root_x as i32, reply.root_y as i32))
}
+ fn screenshot(&mut self, path: &str, annotate: bool) -> Result {
+ let mut image = self.capture_root_image()?;
+
+ if annotate {
+ let window_infos = self.collect_window_infos()?;
+ annotate_screenshot(&mut image, &window_infos);
+ }
+
+ image.save(path).context("Failed to save screenshot")?;
+ Ok(path.to_string())
+ }
+
fn launch(&self, command: &str, args: &[String]) -> Result {
let child = std::process::Command::new(command)
.args(args)
@@ -553,10 +485,6 @@ impl super::DesktopBackend for X11Backend {
.with_context(|| format!("Failed to launch: {command}"))?;
Ok(child.id())
}
-
- fn backend_name(&self) -> &'static str {
- "x11"
- }
}
fn parse_key(name: &str) -> Result {
diff --git a/src/cli/connection.rs b/src/cli/connection.rs
index 1b7b0b2..f960ee3 100644
--- a/src/cli/connection.rs
+++ b/src/cli/connection.rs
@@ -1,7 +1,7 @@
use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::UnixStream;
use std::os::unix::process::CommandExt;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;
@@ -9,48 +9,33 @@ use std::time::Duration;
use anyhow::{bail, Context, Result};
use crate::cli::GlobalOpts;
-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};
+fn socket_dir() -> PathBuf {
+ if let Ok(dir) = std::env::var("DESKCTL_SOCKET_DIR") {
+ return PathBuf::from(dir);
+ }
+ if let Ok(runtime) = std::env::var("XDG_RUNTIME_DIR") {
+ return PathBuf::from(runtime).join("deskctl");
+ }
+ dirs::home_dir()
+ .unwrap_or_else(|| PathBuf::from("/tmp"))
+ .join(".deskctl")
+}
+
fn socket_path(opts: &GlobalOpts) -> PathBuf {
if let Some(ref path) = opts.socket {
return path.clone();
}
- socket_path_for_session(&opts.session)
+ socket_dir().join(format!("{}.sock", opts.session))
}
fn pid_path(opts: &GlobalOpts) -> PathBuf {
- pid_path_for_session(&opts.session)
+ socket_dir().join(format!("{}.pid", opts.session))
}
-fn connect_socket(path: &Path) -> Result {
- UnixStream::connect(path).with_context(|| format!("Failed to connect to {}", path.display()))
-}
-
-fn is_stale_socket_error(error: &std::io::Error) -> bool {
- matches!(
- error.kind(),
- std::io::ErrorKind::ConnectionRefused | std::io::ErrorKind::NotFound
- )
-}
-
-fn cleanup_stale_socket(opts: &GlobalOpts) -> Result {
- let path = socket_path(opts);
- if !path.exists() {
- return Ok(false);
- }
-
- match UnixStream::connect(&path) {
- Ok(_) => Ok(false),
- Err(error) if is_stale_socket_error(&error) => {
- std::fs::remove_file(&path)
- .with_context(|| format!("Failed to remove stale socket {}", path.display()))?;
- Ok(true)
- }
- Err(error) => Err(error)
- .with_context(|| format!("Failed to inspect daemon socket {}", path.display())),
- }
+fn try_connect(opts: &GlobalOpts) -> Option {
+ UnixStream::connect(socket_path(opts)).ok()
}
fn spawn_daemon(opts: &GlobalOpts) -> Result<()> {
@@ -66,8 +51,9 @@ fn spawn_daemon(opts: &GlobalOpts) -> Result<()> {
.env("DESKCTL_PID_PATH", pid_path(opts))
.stdin(Stdio::null())
.stdout(Stdio::null())
- .stderr(Stdio::null());
+ .stderr(Stdio::piped());
+ // Detach the daemon process on Unix
unsafe {
cmd.pre_exec(|| {
libc::setsid();
@@ -79,136 +65,82 @@ fn spawn_daemon(opts: &GlobalOpts) -> Result<()> {
Ok(())
}
-fn request_read_timeout(request: &Request) -> Duration {
- let default_timeout = Duration::from_secs(30);
- match request.action.as_str() {
- "wait-window" | "wait-focus" => {
- let wait_timeout = request
- .extra
- .get("timeout_ms")
- .and_then(|value| value.as_u64())
- .unwrap_or(10_000);
- Duration::from_millis(wait_timeout.saturating_add(5_000))
- }
- _ => default_timeout,
- }
-}
-
-fn send_request_over_stream(mut stream: UnixStream, request: &Request) -> Result {
- stream.set_read_timeout(Some(request_read_timeout(request)))?;
- stream.set_write_timeout(Some(Duration::from_secs(5)))?;
-
- let json = serde_json::to_string(request)?;
- writeln!(stream, "{json}")?;
- stream.flush()?;
-
- let mut reader = BufReader::new(&stream);
- let mut line = String::new();
- reader.read_line(&mut line)?;
-
- serde_json::from_str(line.trim()).context("Failed to parse daemon response")
-}
-
-fn ping_daemon(opts: &GlobalOpts) -> Result<()> {
- let response =
- send_request_over_stream(connect_socket(&socket_path(opts))?, &Request::new("ping"))?;
- if response.success {
- Ok(())
- } else {
- bail!(
- "{}",
- response
- .error
- .unwrap_or_else(|| "Daemon health probe failed".to_string())
- );
- }
-}
-
fn ensure_daemon(opts: &GlobalOpts) -> Result {
- if ping_daemon(opts).is_ok() {
- return connect_socket(&socket_path(opts));
- }
-
- let removed_stale_socket = cleanup_stale_socket(opts)?;
- if removed_stale_socket && ping_daemon(opts).is_ok() {
- return connect_socket(&socket_path(opts));
+ // Try connecting first
+ if let Some(stream) = try_connect(opts) {
+ return Ok(stream);
}
+ // Spawn daemon
spawn_daemon(opts)?;
+ // Retry with backoff
let max_retries = 20;
let base_delay = Duration::from_millis(50);
- for attempt in 0..max_retries {
- thread::sleep(base_delay * (attempt + 1).min(4));
- if ping_daemon(opts).is_ok() {
- return connect_socket(&socket_path(opts));
+ for i in 0..max_retries {
+ thread::sleep(base_delay * (i + 1).min(4));
+ if let Some(stream) = try_connect(opts) {
+ return Ok(stream);
}
}
bail!(
- "Failed to start a healthy daemon after {} retries.\nSocket path: {}",
+ "Failed to connect to daemon after {} retries.\n\
+ Socket path: {}",
max_retries,
socket_path(opts).display()
);
}
pub fn send_command(opts: &GlobalOpts, request: &Request) -> Result {
- send_request_over_stream(ensure_daemon(opts)?, request)
+ let mut stream = ensure_daemon(opts)?;
+ stream.set_read_timeout(Some(Duration::from_secs(30)))?;
+ stream.set_write_timeout(Some(Duration::from_secs(5)))?;
+
+ // Send NDJSON request
+ let json = serde_json::to_string(request)?;
+ writeln!(stream, "{json}")?;
+ stream.flush()?;
+
+ // Read NDJSON response
+ let mut reader = BufReader::new(&stream);
+ let mut line = String::new();
+ reader.read_line(&mut line)?;
+
+ let response: Response =
+ serde_json::from_str(line.trim()).context("Failed to parse daemon response")?;
+
+ Ok(response)
}
-pub fn run_doctor(opts: &GlobalOpts) -> Result<()> {
- let report = run_doctor_report(&socket_path(opts));
- print_doctor_report(&report, opts.json)?;
- if !report.healthy {
- std::process::exit(1);
+pub fn start_daemon(opts: &GlobalOpts) -> Result<()> {
+ if try_connect(opts).is_some() {
+ println!("Daemon already running ({})", socket_path(opts).display());
+ return Ok(());
+ }
+ spawn_daemon(opts)?;
+ // Wait briefly and verify
+ thread::sleep(Duration::from_millis(200));
+ if try_connect(opts).is_some() {
+ println!("Daemon started ({})", socket_path(opts).display());
+ } else {
+ bail!("Daemon failed to start");
}
Ok(())
}
-pub fn start_daemon(opts: &GlobalOpts) -> Result<()> {
- if ping_daemon(opts).is_ok() {
- println!("Daemon already running ({})", socket_path(opts).display());
- return Ok(());
- }
-
- if cleanup_stale_socket(opts)? {
- println!("Removed stale socket: {}", socket_path(opts).display());
- }
-
- spawn_daemon(opts)?;
-
- let max_retries = 20;
- for attempt in 0..max_retries {
- thread::sleep(Duration::from_millis(50 * (attempt + 1).min(4) as u64));
- if ping_daemon(opts).is_ok() {
- println!("Daemon started ({})", socket_path(opts).display());
- return Ok(());
- }
- }
-
- bail!(
- "Daemon failed to become healthy.\nSocket path: {}",
- socket_path(opts).display()
- );
-}
-
pub fn stop_daemon(opts: &GlobalOpts) -> Result<()> {
- let path = socket_path(opts);
- match UnixStream::connect(&path) {
- Ok(stream) => {
- let response = send_request_over_stream(stream, &Request::new("shutdown"))?;
- if response.success {
- println!("Daemon stopped");
- } else {
- bail!(
- "{}",
- response
- .error
- .unwrap_or_else(|| "Failed to stop daemon".to_string())
- );
- }
+ match try_connect(opts) {
+ Some(mut stream) => {
+ let req = Request::new("shutdown");
+ let json = serde_json::to_string(&req)?;
+ writeln!(stream, "{json}")?;
+ stream.flush()?;
+ println!("Daemon stopped");
}
- Err(error) if is_stale_socket_error(&error) => {
+ None => {
+ // Try to clean up stale socket
+ let path = socket_path(opts);
if path.exists() {
std::fs::remove_file(&path)?;
println!("Removed stale socket: {}", path.display());
@@ -216,78 +148,15 @@ pub fn stop_daemon(opts: &GlobalOpts) -> Result<()> {
println!("Daemon not running");
}
}
- Err(error) => {
- return Err(error)
- .with_context(|| format!("Failed to inspect daemon socket {}", path.display()));
- }
}
Ok(())
}
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(_) => println!("Daemon not running"),
+ if try_connect(opts).is_some() {
+ println!("Daemon running ({})", socket_path(opts).display());
+ } else {
+ println!("Daemon not running");
}
Ok(())
}
-
-fn print_doctor_report(report: &DoctorReport, json_output: bool) -> Result<()> {
- if json_output {
- println!("{}", serde_json::to_string_pretty(report)?);
- return Ok(());
- }
-
- println!(
- "deskctl doctor: {}",
- if report.healthy {
- "healthy"
- } else {
- "issues found"
- }
- );
- for check in &report.checks {
- let status = if check.ok { "OK" } else { "FAIL" };
- println!("[{status}] {}: {}", check.name, check.details);
- if let Some(fix) = &check.fix {
- println!(" fix: {fix}");
- }
- }
- Ok(())
-}
-
-#[cfg(test)]
-mod tests {
- use super::{cleanup_stale_socket, socket_path};
- use crate::cli::GlobalOpts;
- use std::time::{SystemTime, UNIX_EPOCH};
-
- #[test]
- fn cleanup_stale_socket_removes_refused_socket_path() {
- let temp = std::env::temp_dir().join(format!(
- "deskctl-test-{}",
- SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .unwrap()
- .as_nanos()
- ));
- std::fs::create_dir_all(&temp).unwrap();
- let opts = GlobalOpts {
- socket: Some(temp.join("stale.sock")),
- session: "test".to_string(),
- json: false,
- };
-
- let listener = std::os::unix::net::UnixListener::bind(socket_path(&opts)).unwrap();
- drop(listener);
-
- assert!(cleanup_stale_socket(&opts).unwrap());
- assert!(!socket_path(&opts).exists());
-
- let _ = std::fs::remove_dir_all(&temp);
- }
-}
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index 79008de..51891c0 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -1,5 +1,4 @@
-pub mod connection;
-pub mod upgrade;
+mod connection;
use anyhow::Result;
use clap::{Args, Parser, Subcommand};
@@ -8,12 +7,7 @@ use std::path::PathBuf;
use crate::core::protocol::{Request, Response};
#[derive(Parser)]
-#[command(
- name = "deskctl",
- bin_name = "deskctl",
- version,
- about = "Desktop control CLI for AI agents"
-)]
+#[command(name = "deskctl", version, about = "Desktop control CLI for AI agents")]
pub struct App {
#[command(flatten)]
pub global: GlobalOpts,
@@ -39,38 +33,32 @@ pub struct GlobalOpts {
#[derive(Subcommand)]
pub enum Command {
/// Take a screenshot and list windows with @wN refs
- #[command(after_help = SNAPSHOT_EXAMPLES)]
Snapshot {
/// Draw bounding boxes and labels on the screenshot
#[arg(long)]
annotate: bool,
},
/// Click a window ref or coordinates
- #[command(after_help = CLICK_EXAMPLES)]
Click {
- /// Selector (ref=w1, id=win1, title=Chromium, class=chromium, focused) or x,y coordinates
+ /// @w1 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=Chromium, class=chromium, focused) or x,y coordinates
+ /// @w1 or x,y coordinates
selector: String,
},
/// Type text into the focused window
- #[command(after_help = TYPE_EXAMPLES)]
Type {
/// Text to type
text: String,
},
/// Press a key (e.g. enter, tab, escape)
- #[command(after_help = PRESS_EXAMPLES)]
Press {
/// Key name
key: String,
},
/// Send a hotkey combination (e.g. ctrl c)
- #[command(after_help = HOTKEY_EXAMPLES)]
Hotkey {
/// Key names (e.g. ctrl shift t)
keys: Vec,
@@ -79,21 +67,18 @@ pub enum Command {
#[command(subcommand)]
Mouse(MouseCmd),
/// Focus a window by ref or name
- #[command(after_help = FOCUS_EXAMPLES)]
Focus {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// @w1 or window name substring
selector: String,
},
/// Close a window by ref or name
- #[command(after_help = CLOSE_EXAMPLES)]
Close {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// @w1 or window name substring
selector: String,
},
/// Move a window
- #[command(after_help = MOVE_WINDOW_EXAMPLES)]
MoveWindow {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// @w1 or window name substring
selector: String,
/// X position
x: i32,
@@ -101,9 +86,8 @@ pub enum Command {
y: i32,
},
/// Resize a window
- #[command(after_help = RESIZE_WINDOW_EXAMPLES)]
ResizeWindow {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// @w1 or window name substring
selector: String,
/// Width
w: u32,
@@ -111,28 +95,12 @@ pub enum Command {
h: u32,
},
/// List all windows (same as snapshot but without screenshot)
- #[command(after_help = LIST_WINDOWS_EXAMPLES)]
ListWindows,
/// Get screen resolution
- #[command(after_help = GET_SCREEN_SIZE_EXAMPLES)]
GetScreenSize,
/// Get current mouse position
- #[command(after_help = GET_MOUSE_POSITION_EXAMPLES)]
GetMousePosition,
- /// 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),
- /// Wait for runtime state transitions
- #[command(subcommand)]
- Wait(WaitCmd),
/// Take a screenshot without window tree
- #[command(after_help = SCREENSHOT_EXAMPLES)]
Screenshot {
/// Save path (default: /tmp/deskctl-{timestamp}.png)
path: Option,
@@ -141,7 +109,6 @@ pub enum Command {
annotate: bool,
},
/// Launch an application
- #[command(after_help = LAUNCH_EXAMPLES)]
Launch {
/// Command to run
command: String,
@@ -157,7 +124,6 @@ pub enum Command {
#[derive(Subcommand)]
pub enum MouseCmd {
/// Move the mouse cursor
- #[command(after_help = MOUSE_MOVE_EXAMPLES)]
Move {
/// X coordinate
x: i32,
@@ -165,7 +131,6 @@ pub enum MouseCmd {
y: i32,
},
/// Scroll the mouse wheel
- #[command(after_help = MOUSE_SCROLL_EXAMPLES)]
Scroll {
/// Amount (positive = down, negative = up)
amount: i32,
@@ -174,7 +139,6 @@ pub enum MouseCmd {
axis: String,
},
/// Drag from one position to another
- #[command(after_help = MOUSE_DRAG_EXAMPLES)]
Drag {
/// Start X
x1: i32,
@@ -203,100 +167,6 @@ pub enum DaemonAction {
Status,
}
-const GET_ACTIVE_WINDOW_EXAMPLES: &str =
- "Examples:\n deskctl get active-window\n deskctl --json get active-window";
-const SNAPSHOT_EXAMPLES: &str =
- "Examples:\n deskctl snapshot\n deskctl snapshot --annotate\n deskctl --json snapshot --annotate";
-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=Chromium'\n deskctl click 500,300";
-const DBLCLICK_EXAMPLES: &str =
- "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=Chromium'\n deskctl focus focused";
-const CLOSE_EXAMPLES: &str =
- "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=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 =
- "Examples:\n deskctl get monitors\n deskctl --json get monitors";
-const GET_VERSION_EXAMPLES: &str = "Examples:\n deskctl get version\n deskctl --json get version";
-const GET_SYSTEMINFO_EXAMPLES: &str =
- "Examples:\n deskctl get systeminfo\n deskctl --json get systeminfo";
-const GET_SCREEN_SIZE_EXAMPLES: &str =
- "Examples:\n deskctl get-screen-size\n deskctl --json get-screen-size";
-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=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 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 =
- "Examples:\n deskctl mouse scroll 3\n deskctl mouse scroll -3 --axis vertical";
-const MOUSE_DRAG_EXAMPLES: &str = "Examples:\n deskctl mouse drag 100 100 500 500";
-
-#[derive(Subcommand)]
-pub enum GetCmd {
- /// Show the currently focused window
- #[command(after_help = GET_ACTIVE_WINDOW_EXAMPLES)]
- ActiveWindow,
- /// List current monitor geometry and metadata
- #[command(after_help = GET_MONITORS_EXAMPLES)]
- Monitors,
- /// Show deskctl version and backend information
- #[command(after_help = GET_VERSION_EXAMPLES)]
- Version,
- /// Show runtime-focused diagnostic information
- #[command(after_help = GET_SYSTEMINFO_EXAMPLES)]
- Systeminfo,
-}
-
-#[derive(Subcommand)]
-pub enum WaitCmd {
- /// Wait until a window matching the selector exists
- #[command(after_help = WAIT_WINDOW_EXAMPLES)]
- Window(WaitSelectorOpts),
- /// Wait until the selector resolves to a focused window
- #[command(after_help = WAIT_FOCUS_EXAMPLES)]
- Focus(WaitSelectorOpts),
-}
-
-#[derive(Args)]
-pub struct WaitSelectorOpts {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
- #[arg(long)]
- pub selector: String,
-
- /// Timeout in seconds
- #[arg(long, default_value_t = 10)]
- pub timeout: u64,
-
- /// Poll interval in milliseconds
- #[arg(long = "poll-ms", default_value_t = 250)]
- 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();
@@ -309,36 +179,12 @@ pub fn run() -> Result<()> {
};
}
- if let Command::Doctor = app.command {
- 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)?;
- 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)?;
}
@@ -391,24 +237,6 @@ fn build_request(cmd: &Command) -> Result {
Command::ListWindows => Request::new("list-windows"),
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"),
- GetCmd::Version => Request::new("get-version"),
- GetCmd::Systeminfo => Request::new("get-systeminfo"),
- },
- Command::Wait(sub) => match sub {
- WaitCmd::Window(opts) => Request::new("wait-window")
- .with_extra("selector", json!(opts.selector))
- .with_extra("timeout_ms", json!(opts.timeout * 1000))
- .with_extra("poll_ms", json!(opts.poll_ms)),
- WaitCmd::Focus(opts) => Request::new("wait-focus")
- .with_extra("selector", json!(opts.selector))
- .with_extra("timeout_ms", json!(opts.timeout * 1000))
- .with_extra("poll_ms", json!(opts.poll_ms)),
- },
Command::Screenshot { path, annotate } => {
let mut req = Request::new("screenshot").with_extra("annotate", json!(annotate));
if let Some(p) = path {
@@ -426,794 +254,53 @@ fn build_request(cmd: &Command) -> Result {
fn print_response(cmd: &Command, response: &Response) -> Result<()> {
if !response.success {
- for line in render_error_lines(response) {
- eprintln!("{line}");
+ if let Some(ref err) = response.error {
+ eprintln!("Error: {err}");
}
std::process::exit(1);
}
- for line in render_success_lines(cmd, response.data.as_ref())? {
- println!("{line}");
+ if let Some(ref data) = response.data {
+ // For snapshot, print compact text format
+ if matches!(cmd, Command::Snapshot { .. }) {
+ if let Some(screenshot) = data.get("screenshot").and_then(|v| v.as_str()) {
+ println!("Screenshot: {screenshot}");
+ }
+ if let Some(windows) = data.get("windows").and_then(|v| v.as_array()) {
+ println!("Windows:");
+ for w in windows {
+ let ref_id = w.get("ref_id").and_then(|v| v.as_str()).unwrap_or("?");
+ let title = w.get("title").and_then(|v| v.as_str()).unwrap_or("");
+ let focused = w.get("focused").and_then(|v| v.as_bool()).unwrap_or(false);
+ let minimized = w
+ .get("minimized")
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false);
+ let x = w.get("x").and_then(|v| v.as_i64()).unwrap_or(0);
+ let y = w.get("y").and_then(|v| v.as_i64()).unwrap_or(0);
+ let width = w.get("width").and_then(|v| v.as_u64()).unwrap_or(0);
+ let height = w.get("height").and_then(|v| v.as_u64()).unwrap_or(0);
+ let state = if focused {
+ "focused"
+ } else if minimized {
+ "hidden"
+ } else {
+ "visible"
+ };
+ let display_title = if title.len() > 30 {
+ format!("{}...", &title[..27])
+ } else {
+ title.to_string()
+ };
+ println!(
+ "@{:<4} {:<30} ({:<7}) {},{} {}x{}",
+ ref_id, display_title, state, x, y, width, height
+ );
+ }
+ }
+ } else {
+ // Generic: print JSON data
+ println!("{}", serde_json::to_string_pretty(data)?);
+ }
}
Ok(())
}
-
-fn render_success_lines(cmd: &Command, data: Option<&serde_json::Value>) -> Result> {
- let Some(data) = data else {
- return Ok(vec!["ok".to_string()]);
- };
-
- let lines = match cmd {
- Command::Snapshot { .. } | Command::ListWindows => render_window_listing(data),
- Command::Get(GetCmd::ActiveWindow)
- | Command::Wait(WaitCmd::Window(_))
- | Command::Wait(WaitCmd::Focus(_)) => render_window_wait_or_read(data),
- Command::Get(GetCmd::Monitors) => render_monitor_listing(data),
- Command::Get(GetCmd::Version) => vec![render_version_line(data)],
- 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)],
- Command::Type { .. } => vec![render_type_line(data)],
- Command::Press { .. } => vec![render_press_line(data)],
- Command::Hotkey { .. } => vec![render_hotkey_line(data)],
- Command::Mouse(sub) => vec![render_mouse_line(sub, data)],
- Command::Focus { .. } => vec![render_window_action_line("Focused", data)],
- Command::Close { .. } => vec![render_window_action_line("Closed", data)],
- Command::MoveWindow { .. } => vec![render_move_window_line(data)],
- Command::ResizeWindow { .. } => vec![render_resize_window_line(data)],
- Command::Launch { .. } => vec![render_launch_line(data)],
- Command::Doctor | Command::Daemon(_) => vec![serde_json::to_string_pretty(data)?],
- };
-
- Ok(lines)
-}
-
-fn render_error_lines(response: &Response) -> Vec {
- let mut lines = Vec::new();
- if let Some(err) = &response.error {
- lines.push(format!("Error: {err}"));
- }
-
- let Some(data) = response.data.as_ref() else {
- return lines;
- };
-
- let Some(kind) = data.get("kind").and_then(|value| value.as_str()) else {
- return lines;
- };
-
- match kind {
- "selector_not_found" => {
- let selector = data
- .get("selector")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- let mode = data
- .get("mode")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- lines.push(format!("Selector: {selector} (mode: {mode})"));
- }
- "selector_invalid" => {
- let selector = data
- .get("selector")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- let mode = data
- .get("mode")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- lines.push(format!("Selector: {selector} (mode: {mode})"));
- if let Some(message) = data.get("message").and_then(|value| value.as_str()) {
- lines.push(format!("Reason: {message}"));
- }
- }
- "selector_ambiguous" => {
- let selector = data
- .get("selector")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- let mode = data
- .get("mode")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- lines.push(format!("Selector: {selector} (mode: {mode})"));
- if let Some(candidates) = data.get("candidates").and_then(|value| value.as_array()) {
- lines.push("Candidates:".to_string());
- for candidate in candidates {
- lines.push(window_line(candidate));
- }
- }
- }
- "timeout" => {
- let selector = data
- .get("selector")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- let wait = data
- .get("wait")
- .and_then(|value| value.as_str())
- .unwrap_or("wait");
- let timeout_ms = data
- .get("timeout_ms")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- lines.push(format!(
- "Timed out after {timeout_ms}ms waiting for {wait} selector {selector}"
- ));
- if let Some(observation) = data.get("last_observation") {
- lines.extend(render_last_observation_lines(observation));
- }
- }
- "not_found" => {
- if data
- .get("mode")
- .and_then(|value| value.as_str())
- .is_some_and(|mode| mode == "focused")
- {
- 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}"));
- }
- }
- _ => {}
- }
-
- lines
-}
-
-fn render_last_observation_lines(observation: &serde_json::Value) -> Vec {
- let mut lines = Vec::new();
- let Some(kind) = observation.get("kind").and_then(|value| value.as_str()) else {
- return lines;
- };
-
- match kind {
- "window_not_focused" => {
- lines.push(
- "Last observation: matching window exists but is not focused yet.".to_string(),
- );
- if let Some(window) = observation.get("window") {
- lines.push(window_line(window));
- }
- }
- "selector_not_found" => {
- let selector = observation
- .get("selector")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- let mode = observation
- .get("mode")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- lines.push(format!(
- "Last observation: no window matched selector {selector} (mode: {mode})"
- ));
- }
- _ => {
- lines.push(format!(
- "Last observation: {}",
- serde_json::to_string(observation).unwrap_or_else(|_| kind.to_string())
- ));
- }
- }
-
- lines
-}
-
-fn render_window_listing(data: &serde_json::Value) -> Vec {
- let mut lines = Vec::new();
- if let Some(screenshot) = data.get("screenshot").and_then(|value| value.as_str()) {
- lines.push(format!("Screenshot: {screenshot}"));
- }
- if let Some(windows) = data.get("windows").and_then(|value| value.as_array()) {
- lines.push(format!("Windows: {}", windows.len()));
- for window in windows {
- lines.push(window_line(window));
- }
- }
- lines
-}
-
-fn render_window_wait_or_read(data: &serde_json::Value) -> Vec {
- let mut lines = Vec::new();
- if let Some(window) = data.get("window") {
- lines.push(window_line(window));
- }
- if let Some(elapsed_ms) = data.get("elapsed_ms").and_then(|value| value.as_u64()) {
- lines.push(format!("Elapsed: {elapsed_ms}ms"));
- }
- lines
-}
-
-fn render_monitor_listing(data: &serde_json::Value) -> Vec {
- let mut lines = Vec::new();
- if let Some(count) = data.get("count").and_then(|value| value.as_u64()) {
- lines.push(format!("Monitors: {count}"));
- }
- if let Some(monitors) = data.get("monitors").and_then(|value| value.as_array()) {
- for monitor in monitors {
- let name = monitor
- .get("name")
- .and_then(|value| value.as_str())
- .unwrap_or("monitor");
- let x = monitor
- .get("x")
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let y = monitor
- .get("y")
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let width = monitor
- .get("width")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- let height = monitor
- .get("height")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- let primary = monitor
- .get("primary")
- .and_then(|value| value.as_bool())
- .unwrap_or(false);
- let automatic = monitor
- .get("automatic")
- .and_then(|value| value.as_bool())
- .unwrap_or(false);
- let mut flags = Vec::new();
- if primary {
- flags.push("primary");
- }
- if automatic {
- flags.push("automatic");
- }
- let suffix = if flags.is_empty() {
- String::new()
- } else {
- format!(" [{}]", flags.join(", "))
- };
- lines.push(format!("{name:<16} {x},{y} {width}x{height}{suffix}"));
- }
- }
- lines
-}
-
-fn render_version_line(data: &serde_json::Value) -> String {
- let version = data
- .get("version")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- let backend = data
- .get("backend")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- format!("deskctl {version} ({backend})")
-}
-
-fn render_systeminfo_lines(data: &serde_json::Value) -> Vec {
- let mut lines = Vec::new();
- let backend = data
- .get("backend")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- lines.push(format!("Backend: {backend}"));
- if let Some(display) = data.get("display").and_then(|value| value.as_str()) {
- lines.push(format!("Display: {display}"));
- }
- if let Some(session_type) = data.get("session_type").and_then(|value| value.as_str()) {
- lines.push(format!("Session type: {session_type}"));
- }
- if let Some(session) = data.get("session").and_then(|value| value.as_str()) {
- lines.push(format!("Session: {session}"));
- }
- if let Some(socket_path) = data.get("socket_path").and_then(|value| value.as_str()) {
- lines.push(format!("Socket: {socket_path}"));
- }
- if let Some(screen) = data.get("screen") {
- lines.push(format!("Screen: {}", screen_dimensions(screen)));
- }
- if let Some(count) = data.get("monitor_count").and_then(|value| value.as_u64()) {
- lines.push(format!("Monitor count: {count}"));
- }
- if let Some(monitors) = data.get("monitors").and_then(|value| value.as_array()) {
- for monitor in monitors {
- lines.push(format!(
- " {}",
- render_monitor_listing(&serde_json::json!({"monitors": [monitor]}))[0]
- ));
- }
- }
- lines
-}
-
-fn render_screen_size_line(data: &serde_json::Value) -> String {
- format!("Screen: {}", screen_dimensions(data))
-}
-
-fn render_mouse_position_line(data: &serde_json::Value) -> String {
- let x = data.get("x").and_then(|value| value.as_i64()).unwrap_or(0);
- let y = data.get("y").and_then(|value| value.as_i64()).unwrap_or(0);
- format!("Pointer: {x},{y}")
-}
-
-fn render_screenshot_lines(data: &serde_json::Value, annotate: bool) -> Vec {
- let mut lines = Vec::new();
- if let Some(screenshot) = data.get("screenshot").and_then(|value| value.as_str()) {
- lines.push(format!("Screenshot: {screenshot}"));
- }
- if annotate {
- if let Some(windows) = data.get("windows").and_then(|value| value.as_array()) {
- lines.push(format!("Annotated windows: {}", windows.len()));
- for window in windows {
- lines.push(window_line(window));
- }
- }
- }
- lines
-}
-
-fn render_upgrade_lines(data: &serde_json::Value) -> 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" };
- let x = data
- .get(key)
- .and_then(|value| value.get("x"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let y = data
- .get(key)
- .and_then(|value| value.get("y"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- match target_summary(data) {
- Some(target) => format!("{action} {x},{y} on {target}"),
- None => format!("{action} {x},{y}"),
- }
-}
-
-fn render_type_line(data: &serde_json::Value) -> String {
- let typed = data
- .get("typed")
- .and_then(|value| value.as_str())
- .unwrap_or("");
- format!("Typed: {}", quoted_summary(typed, 60))
-}
-
-fn render_press_line(data: &serde_json::Value) -> String {
- let key = data
- .get("pressed")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- format!("Pressed: {key}")
-}
-
-fn render_hotkey_line(data: &serde_json::Value) -> String {
- let keys = data
- .get("hotkey")
- .and_then(|value| value.as_array())
- .map(|items| {
- items
- .iter()
- .filter_map(|value| value.as_str())
- .collect::>()
- .join("+")
- })
- .filter(|value| !value.is_empty())
- .unwrap_or_else(|| "unknown".to_string());
- format!("Hotkey: {keys}")
-}
-
-fn render_mouse_line(sub: &MouseCmd, data: &serde_json::Value) -> String {
- match sub {
- MouseCmd::Move { .. } => {
- let x = data
- .get("moved")
- .and_then(|value| value.get("x"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let y = data
- .get("moved")
- .and_then(|value| value.get("y"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- format!("Moved pointer to {x},{y}")
- }
- MouseCmd::Scroll { .. } => {
- let amount = data
- .get("scrolled")
- .and_then(|value| value.get("amount"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let axis = data
- .get("scrolled")
- .and_then(|value| value.get("axis"))
- .and_then(|value| value.as_str())
- .unwrap_or("vertical");
- format!("Scrolled {axis} by {amount}")
- }
- MouseCmd::Drag { .. } => {
- let x1 = data
- .get("dragged")
- .and_then(|value| value.get("from"))
- .and_then(|value| value.get("x"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let y1 = data
- .get("dragged")
- .and_then(|value| value.get("from"))
- .and_then(|value| value.get("y"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let x2 = data
- .get("dragged")
- .and_then(|value| value.get("to"))
- .and_then(|value| value.get("x"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let y2 = data
- .get("dragged")
- .and_then(|value| value.get("to"))
- .and_then(|value| value.get("y"))
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- format!("Dragged {x1},{y1} -> {x2},{y2}")
- }
- }
-}
-
-fn render_window_action_line(action: &str, data: &serde_json::Value) -> String {
- match target_summary(data) {
- Some(target) => format!("{action} {target}"),
- None => action.to_string(),
- }
-}
-
-fn render_move_window_line(data: &serde_json::Value) -> String {
- let x = data.get("x").and_then(|value| value.as_i64()).unwrap_or(0);
- let y = data.get("y").and_then(|value| value.as_i64()).unwrap_or(0);
- match target_summary(data) {
- Some(target) => format!("Moved {target} to {x},{y}"),
- None => format!("Moved window to {x},{y}"),
- }
-}
-
-fn render_resize_window_line(data: &serde_json::Value) -> String {
- let width = data
- .get("width")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- let height = data
- .get("height")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- match target_summary(data) {
- Some(target) => format!("Resized {target} to {width}x{height}"),
- None => format!("Resized window to {width}x{height}"),
- }
-}
-
-fn render_launch_line(data: &serde_json::Value) -> String {
- let command = data
- .get("command")
- .and_then(|value| value.as_str())
- .unwrap_or("command");
- let pid = data
- .get("pid")
- .and_then(|value| value.as_u64())
- .map(|value| value.to_string())
- .unwrap_or_else(|| "unknown".to_string());
- format!("Launched {command} (pid {pid})")
-}
-
-fn window_line(window: &serde_json::Value) -> String {
- let ref_id = window
- .get("ref_id")
- .and_then(|value| value.as_str())
- .unwrap_or("?");
- let window_id = window
- .get("window_id")
- .and_then(|value| value.as_str())
- .unwrap_or("unknown");
- let title = window
- .get("title")
- .and_then(|value| value.as_str())
- .unwrap_or("");
- let focused = window
- .get("focused")
- .and_then(|value| value.as_bool())
- .unwrap_or(false);
- let minimized = window
- .get("minimized")
- .and_then(|value| value.as_bool())
- .unwrap_or(false);
- let x = window
- .get("x")
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let y = window
- .get("y")
- .and_then(|value| value.as_i64())
- .unwrap_or(0);
- let width = window
- .get("width")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- let height = window
- .get("height")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- let state = if focused {
- "focused"
- } else if minimized {
- "hidden"
- } else {
- "visible"
- };
- format!(
- "@{ref_id:<4} {:<30} ({state:<7}) {x},{y} {width}x{height} [{window_id}]",
- truncate_display(title, 30)
- )
-}
-
-fn target_summary(data: &serde_json::Value) -> Option {
- let ref_id = data.get("ref_id").and_then(|value| value.as_str());
- let window_id = data.get("window_id").and_then(|value| value.as_str());
- let title = data
- .get("title")
- .or_else(|| data.get("window"))
- .and_then(|value| value.as_str());
-
- match (ref_id, window_id, title) {
- (Some(ref_id), Some(window_id), Some(title)) => Some(format!(
- "@{ref_id} [{window_id}] {}",
- quoted_summary(title, 40)
- )),
- (None, Some(window_id), Some(title)) => {
- Some(format!("[{window_id}] {}", quoted_summary(title, 40)))
- }
- (Some(ref_id), Some(window_id), None) => Some(format!("@{ref_id} [{window_id}]")),
- (None, Some(window_id), None) => Some(format!("[{window_id}]")),
- _ => None,
- }
-}
-
-fn quoted_summary(value: &str, max_chars: usize) -> String {
- format!("\"{}\"", truncate_display(value, max_chars))
-}
-
-fn screen_dimensions(data: &serde_json::Value) -> String {
- let width = data
- .get("width")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- let height = data
- .get("height")
- .and_then(|value| value.as_u64())
- .unwrap_or(0);
- format!("{width}x{height}")
-}
-
-fn truncate_display(value: &str, max_chars: usize) -> String {
- let char_count = value.chars().count();
- if char_count <= max_chars {
- return value.to_string();
- }
-
- let truncated: String = value.chars().take(max_chars.saturating_sub(3)).collect();
- format!("{truncated}...")
-}
-
-#[cfg(test)]
-mod tests {
- use super::{
- render_error_lines, render_screen_size_line, render_success_lines, target_summary,
- truncate_display, App, Command, Response, UpgradeOpts,
- };
- use clap::CommandFactory;
- use serde_json::json;
-
- #[test]
- fn help_examples_include_snapshot_examples() {
- let help = App::command()
- .find_subcommand_mut("snapshot")
- .expect("snapshot subcommand must exist")
- .render_long_help()
- .to_string();
- 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(
- &Command::ListWindows,
- Some(&json!({
- "windows": [{
- "ref_id": "w1",
- "window_id": "win1",
- "title": "Chromium",
- "app_name": "chromium",
- "x": 0,
- "y": 0,
- "width": 1280,
- "height": 720,
- "focused": true,
- "minimized": false
- }]
- })),
- )
- .unwrap();
-
- assert_eq!(lines[0], "Windows: 1");
- assert!(lines[1].contains("[win1]"));
- assert!(lines[1].contains("@w1"));
- }
-
- #[test]
- fn action_text_includes_target_identity() {
- let lines = render_success_lines(
- &Command::Focus {
- selector: "title=Chromium".to_string(),
- },
- Some(&json!({
- "action": "focus",
- "window": "Chromium",
- "title": "Chromium",
- "ref_id": "w2",
- "window_id": "win7"
- })),
- )
- .unwrap();
-
- 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=Chromium",
- json!({
- "kind": "timeout",
- "wait": "focus",
- "selector": "title=Chromium",
- "timeout_ms": 1000,
- "last_observation": {
- "kind": "window_not_focused",
- "window": {
- "ref_id": "w1",
- "window_id": "win1",
- "title": "Chromium",
- "app_name": "chromium",
- "x": 0,
- "y": 0,
- "width": 1280,
- "height": 720,
- "focused": false,
- "minimized": false
- }
- }
- }),
- ));
-
- 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")));
- assert!(lines.iter().any(|line| line.contains("[win1]")));
- }
-
- #[test]
- fn screen_size_text_is_compact() {
- assert_eq!(
- render_screen_size_line(&json!({"width": 1440, "height": 900})),
- "Screen: 1440x900"
- );
- }
-
- #[test]
- fn target_summary_prefers_ref_and_window_id() {
- let summary = target_summary(&json!({
- "ref_id": "w1",
- "window_id": "win1",
- "title": "Chromium"
- }));
- assert_eq!(summary.as_deref(), Some("@w1 [win1] \"Chromium\""));
- }
-
- #[test]
- fn truncate_display_is_char_safe() {
- 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
deleted file mode 100644
index acc844e..0000000
--- a/src/cli/upgrade.rs
+++ /dev/null
@@ -1,465 +0,0 @@
-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");
- }
-}
diff --git a/src/core/doctor.rs b/src/core/doctor.rs
deleted file mode 100644
index e9c4b99..0000000
--- a/src/core/doctor.rs
+++ /dev/null
@@ -1,211 +0,0 @@
-use std::io::{BufRead, BufReader, Write};
-use std::os::unix::net::UnixStream;
-use std::path::Path;
-use std::time::Duration;
-
-use anyhow::Result;
-use serde::Serialize;
-
-use crate::backend::{x11::X11Backend, DesktopBackend};
-use crate::core::protocol::{Request, Response};
-use crate::core::session::detect_session;
-
-#[derive(Debug, Serialize)]
-pub struct DoctorReport {
- pub healthy: bool,
- pub checks: Vec,
-}
-
-#[derive(Debug, Serialize)]
-pub struct DoctorCheck {
- pub name: String,
- pub ok: bool,
- pub details: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub fix: Option,
-}
-
-pub fn run(socket_path: &Path) -> DoctorReport {
- let mut checks = Vec::new();
-
- let display = std::env::var("DISPLAY").ok();
- checks.push(match display {
- Some(ref value) if !value.is_empty() => check_ok("display", format!("DISPLAY={value}")),
- _ => check_fail(
- "display",
- "DISPLAY is not set".to_string(),
- "Export DISPLAY to point at the active X11 server.".to_string(),
- ),
- });
-
- checks.push(match detect_session() {
- Ok(_) => check_ok("session", "X11 session detected".to_string()),
- Err(error) => check_fail(
- "session",
- error.to_string(),
- "Run deskctl inside an X11 session. Wayland is not supported in this phase."
- .to_string(),
- ),
- });
-
- let mut backend = match X11Backend::new() {
- Ok(backend) => {
- checks.push(check_ok(
- "backend",
- "Connected to the X11 backend successfully".to_string(),
- ));
- Some(backend)
- }
- Err(error) => {
- checks.push(check_fail(
- "backend",
- error.to_string(),
- "Ensure the X server is reachable and the current session can access it."
- .to_string(),
- ));
- None
- }
- };
-
- if let Some(backend) = backend.as_mut() {
- checks.push(match backend.list_windows() {
- Ok(windows) => check_ok(
- "window-enumeration",
- format!("Enumerated {} visible windows", windows.len()),
- ),
- Err(error) => check_fail(
- "window-enumeration",
- error.to_string(),
- "Verify the desktop session exposes EWMH window metadata and the X11 connection is healthy."
- .to_string(),
- ),
- });
-
- checks.push(match backend.capture_screenshot() {
- Ok(image) => check_ok(
- "screenshot",
- 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(),
- ),
- });
- } else {
- checks.push(check_fail(
- "window-enumeration",
- "Skipped because backend initialization failed".to_string(),
- "Fix the X11 backend error before retrying.".to_string(),
- ));
- checks.push(check_fail(
- "screenshot",
- "Skipped because backend initialization failed".to_string(),
- "Fix the X11 backend error before retrying.".to_string(),
- ));
- }
-
- checks.push(check_socket_dir(socket_path));
- checks.push(check_daemon_socket(socket_path));
-
- let healthy = checks.iter().all(|check| check.ok);
- DoctorReport { healthy, checks }
-}
-
-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()
- ),
- "Use a socket path inside a writable directory.".to_string(),
- );
- };
-
- match std::fs::create_dir_all(socket_dir) {
- Ok(()) => check_ok(
- "socket-dir",
- format!("Socket directory is ready at {}", socket_dir.display()),
- ),
- Err(error) => check_fail(
- "socket-dir",
- error.to_string(),
- format!("Ensure {} exists and is writable.", socket_dir.display()),
- ),
- }
-}
-
-fn check_daemon_socket(socket_path: &Path) -> DoctorCheck {
- if !socket_path.exists() {
- return check_ok(
- "daemon-socket",
- format!("No stale socket found at {}", socket_path.display()),
- );
- }
-
- match ping_socket(socket_path) {
- Ok(()) => check_ok(
- "daemon-socket",
- format!("Daemon is healthy at {}", socket_path.display()),
- ),
- Err(error) => check_fail(
- "daemon-socket",
- error.to_string(),
- format!(
- "Remove the stale socket at {} or run `deskctl daemon stop`.",
- socket_path.display()
- ),
- ),
- }
-}
-
-fn ping_socket(socket_path: &Path) -> Result<()> {
- let mut stream = UnixStream::connect(socket_path)?;
- stream.set_read_timeout(Some(Duration::from_secs(1)))?;
- stream.set_write_timeout(Some(Duration::from_secs(1)))?;
-
- let request = Request::new("ping");
- let json = serde_json::to_string(&request)?;
- writeln!(stream, "{json}")?;
- stream.flush()?;
-
- let mut reader = BufReader::new(&stream);
- let mut line = String::new();
- reader.read_line(&mut line)?;
- let response: Response = serde_json::from_str(line.trim())?;
-
- if response.success {
- Ok(())
- } else {
- anyhow::bail!(
- "{}",
- response
- .error
- .unwrap_or_else(|| "Daemon health probe failed".to_string())
- )
- }
-}
-
-fn check_ok(name: &str, details: String) -> DoctorCheck {
- DoctorCheck {
- name: name.to_string(),
- ok: true,
- details,
- fix: None,
- }
-}
-
-fn check_fail(name: &str, details: String, fix: String) -> DoctorCheck {
- DoctorCheck {
- name: name.to_string(),
- ok: false,
- details,
- fix: Some(fix),
- }
-}
diff --git a/src/core/mod.rs b/src/core/mod.rs
index 07f3c01..95ab91e 100644
--- a/src/core/mod.rs
+++ b/src/core/mod.rs
@@ -1,5 +1,3 @@
-pub mod doctor;
-pub mod paths;
pub mod protocol;
pub mod refs;
pub mod session;
diff --git a/src/core/paths.rs b/src/core/paths.rs
deleted file mode 100644
index 1d657ec..0000000
--- a/src/core/paths.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use std::path::PathBuf;
-
-pub fn socket_dir() -> PathBuf {
- if let Ok(dir) = std::env::var("DESKCTL_SOCKET_DIR") {
- return PathBuf::from(dir);
- }
- if let Ok(runtime) = std::env::var("XDG_RUNTIME_DIR") {
- return PathBuf::from(runtime).join("deskctl");
- }
- dirs::home_dir()
- .unwrap_or_else(|| PathBuf::from("/tmp"))
- .join(".deskctl")
-}
-
-pub fn socket_path_for_session(session: &str) -> PathBuf {
- socket_dir().join(format!("{session}.sock"))
-}
-
-pub fn pid_path_for_session(session: &str) -> PathBuf {
- socket_dir().join(format!("{session}.pid"))
-}
-
-pub fn socket_path_from_env() -> Option {
- std::env::var("DESKCTL_SOCKET_PATH").ok().map(PathBuf::from)
-}
-
-pub fn pid_path_from_env() -> Option {
- std::env::var("DESKCTL_PID_PATH").ok().map(PathBuf::from)
-}
diff --git a/src/core/protocol.rs b/src/core/protocol.rs
index 8feb87e..c0ead03 100644
--- a/src/core/protocol.rs
+++ b/src/core/protocol.rs
@@ -58,12 +58,4 @@ impl Response {
error: Some(msg.into()),
}
}
-
- pub fn err_with_data(msg: impl Into, data: Value) -> Self {
- Self {
- success: false,
- data: Some(data),
- error: Some(msg.into()),
- }
- }
}
diff --git a/src/core/refs.rs b/src/core/refs.rs
index 7fd7b6c..7909d0e 100644
--- a/src/core/refs.rs
+++ b/src/core/refs.rs
@@ -1,15 +1,10 @@
use serde::{Deserialize, Serialize};
-use std::collections::{HashMap, HashSet};
-
-use crate::backend::BackendWindow;
-use crate::core::types::WindowInfo;
+use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct RefEntry {
- pub ref_id: String,
- pub window_id: String,
- pub backend_window_id: u32,
+ pub xcb_id: u32,
pub app_class: String,
pub title: String,
pub pid: u32,
@@ -24,462 +19,58 @@ pub struct RefEntry {
#[derive(Debug, Default)]
#[allow(dead_code)]
pub struct RefMap {
- refs: HashMap,
- window_id_to_ref: HashMap,
- backend_id_to_window_id: HashMap,
+ map: HashMap,
next_ref: usize,
- next_window: usize,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum SelectorQuery {
- Ref(String),
- WindowId(String),
- Title(String),
- Class(String),
- Focused,
- Fuzzy(String),
-}
-
-#[derive(Debug, Clone)]
-pub enum ResolveResult {
- Match(RefEntry),
- NotFound {
- selector: String,
- mode: &'static str,
- },
- Ambiguous {
- selector: String,
- mode: &'static str,
- candidates: Vec,
- },
- Invalid {
- selector: String,
- mode: &'static str,
- message: String,
- },
}
#[allow(dead_code)]
impl RefMap {
pub fn new() -> Self {
Self {
- refs: HashMap::new(),
- window_id_to_ref: HashMap::new(),
- backend_id_to_window_id: HashMap::new(),
+ map: HashMap::new(),
next_ref: 1,
- next_window: 1,
}
}
pub fn clear(&mut self) {
- self.refs.clear();
- self.window_id_to_ref.clear();
+ self.map.clear();
self.next_ref = 1;
}
- pub fn rebuild(&mut self, windows: &[BackendWindow]) -> Vec {
- self.clear();
-
- let active_backend_ids = windows
- .iter()
- .map(|window| window.native_id)
- .collect::>();
- self.backend_id_to_window_id
- .retain(|backend_id, _| active_backend_ids.contains(backend_id));
-
- let mut public_windows = Vec::with_capacity(windows.len());
- for window in windows {
- let ref_id = format!("w{}", self.next_ref);
- self.next_ref += 1;
-
- let window_id = self.window_id_for_backend(window.native_id);
- let entry = RefEntry {
- ref_id: ref_id.clone(),
- window_id: window_id.clone(),
- backend_window_id: window.native_id,
- app_class: window.app_name.clone(),
- title: window.title.clone(),
- pid: 0,
- x: window.x,
- y: window.y,
- width: window.width,
- height: window.height,
- focused: window.focused,
- minimized: window.minimized,
- };
-
- self.window_id_to_ref
- .insert(window_id.clone(), ref_id.clone());
- self.refs.insert(ref_id.clone(), entry);
- public_windows.push(WindowInfo {
- ref_id,
- window_id,
- title: window.title.clone(),
- app_name: window.app_name.clone(),
- x: window.x,
- y: window.y,
- width: window.width,
- height: window.height,
- focused: window.focused,
- minimized: window.minimized,
- });
- }
-
- public_windows
+ pub fn insert(&mut self, entry: RefEntry) -> String {
+ let ref_id = format!("w{}", self.next_ref);
+ self.next_ref += 1;
+ self.map.insert(ref_id.clone(), entry);
+ ref_id
}
- fn window_id_for_backend(&mut self, backend_window_id: u32) -> String {
- if let Some(existing) = self.backend_id_to_window_id.get(&backend_window_id) {
- return existing.clone();
+ /// Resolve a selector to a RefEntry.
+ /// Accepts: "@w1", "w1", "ref=w1", or a substring match on app_class/title.
+ pub fn resolve(&self, selector: &str) -> Option<&RefEntry> {
+ let normalized = selector
+ .strip_prefix('@')
+ .or_else(|| selector.strip_prefix("ref="))
+ .unwrap_or(selector);
+
+ // Try direct ref lookup
+ if let Some(entry) = self.map.get(normalized) {
+ return Some(entry);
}
- let window_id = format!("win{}", self.next_window);
- self.next_window += 1;
- self.backend_id_to_window_id
- .insert(backend_window_id, window_id.clone());
- window_id
- }
-
- pub fn resolve(&self, selector: &str) -> ResolveResult {
- self.resolve_query(SelectorQuery::parse(selector), selector)
+ // Try substring match on app_class or title (case-insensitive)
+ let lower = selector.to_lowercase();
+ self.map.values().find(|e| {
+ e.app_class.to_lowercase().contains(&lower) || e.title.to_lowercase().contains(&lower)
+ })
}
/// Resolve a selector to the center coordinates of the window.
- pub fn resolve_to_center(&self, selector: &str) -> ResolveResult {
+ pub fn resolve_to_center(&self, selector: &str) -> Option<(i32, i32)> {
self.resolve(selector)
+ .map(|e| (e.x + e.width as i32 / 2, e.y + e.height as i32 / 2))
}
pub fn entries(&self) -> impl Iterator- {
- self.refs.iter()
- }
-
- fn resolve_query(&self, query: SelectorQuery, selector: &str) -> ResolveResult {
- match query {
- SelectorQuery::Ref(ref_id) => self
- .refs
- .get(&ref_id)
- .cloned()
- .map(ResolveResult::Match)
- .unwrap_or_else(|| ResolveResult::NotFound {
- selector: selector.to_string(),
- mode: "ref",
- }),
- SelectorQuery::WindowId(window_id) => self
- .window_id_to_ref
- .get(&window_id)
- .and_then(|ref_id| self.refs.get(ref_id))
- .cloned()
- .map(ResolveResult::Match)
- .unwrap_or_else(|| ResolveResult::NotFound {
- selector: selector.to_string(),
- mode: "id",
- }),
- SelectorQuery::Focused => self.resolve_candidates(
- selector,
- "focused",
- self.refs
- .values()
- .filter(|entry| entry.focused)
- .cloned()
- .collect(),
- ),
- SelectorQuery::Title(title) => {
- if title.is_empty() {
- return ResolveResult::Invalid {
- selector: selector.to_string(),
- mode: "title",
- message: "title selectors must not be empty".to_string(),
- };
- }
- self.resolve_candidates(
- selector,
- "title",
- self.refs
- .values()
- .filter(|entry| entry.title.eq_ignore_ascii_case(&title))
- .cloned()
- .collect(),
- )
- }
- SelectorQuery::Class(app_class) => {
- if app_class.is_empty() {
- return ResolveResult::Invalid {
- selector: selector.to_string(),
- mode: "class",
- message: "class selectors must not be empty".to_string(),
- };
- }
- self.resolve_candidates(
- selector,
- "class",
- self.refs
- .values()
- .filter(|entry| entry.app_class.eq_ignore_ascii_case(&app_class))
- .cloned()
- .collect(),
- )
- }
- SelectorQuery::Fuzzy(value) => {
- if let Some(entry) = self.refs.get(&value).cloned() {
- return ResolveResult::Match(entry);
- }
-
- if let Some(entry) = self
- .window_id_to_ref
- .get(&value)
- .and_then(|ref_id| self.refs.get(ref_id))
- .cloned()
- {
- return ResolveResult::Match(entry);
- }
-
- let lower = value.to_lowercase();
- self.resolve_candidates(
- selector,
- "fuzzy",
- self.refs
- .values()
- .filter(|entry| {
- entry.app_class.to_lowercase().contains(&lower)
- || entry.title.to_lowercase().contains(&lower)
- })
- .cloned()
- .collect(),
- )
- }
- }
- }
-
- fn resolve_candidates(
- &self,
- selector: &str,
- mode: &'static str,
- mut candidates: Vec
,
- ) -> ResolveResult {
- candidates.sort_by(|left, right| left.ref_id.cmp(&right.ref_id));
- match candidates.len() {
- 0 => ResolveResult::NotFound {
- selector: selector.to_string(),
- mode,
- },
- 1 => ResolveResult::Match(candidates.remove(0)),
- _ => ResolveResult::Ambiguous {
- selector: selector.to_string(),
- mode,
- candidates: candidates
- .into_iter()
- .map(|entry| entry.to_window_info())
- .collect(),
- },
- }
- }
-}
-
-impl SelectorQuery {
- pub fn parse(selector: &str) -> Self {
- if let Some(value) = selector.strip_prefix('@') {
- return Self::Ref(value.to_string());
- }
- if let Some(value) = selector.strip_prefix("ref=") {
- return Self::Ref(value.to_string());
- }
- if let Some(value) = selector.strip_prefix("id=") {
- return Self::WindowId(value.to_string());
- }
- if let Some(value) = selector.strip_prefix("title=") {
- return Self::Title(value.to_string());
- }
- if let Some(value) = selector.strip_prefix("class=") {
- return Self::Class(value.to_string());
- }
- if selector == "focused" {
- return Self::Focused;
- }
- Self::Fuzzy(selector.to_string())
- }
-
- pub fn needs_live_refresh(&self) -> bool {
- !matches!(self, Self::Ref(_))
- }
-}
-
-impl RefEntry {
- pub fn center(&self) -> (i32, i32) {
- (
- self.x + self.width as i32 / 2,
- self.y + self.height as i32 / 2,
- )
- }
-
- pub fn to_window_info(&self) -> WindowInfo {
- WindowInfo {
- ref_id: self.ref_id.clone(),
- window_id: self.window_id.clone(),
- title: self.title.clone(),
- app_name: self.app_class.clone(),
- x: self.x,
- y: self.y,
- width: self.width,
- height: self.height,
- focused: self.focused,
- minimized: self.minimized,
- }
- }
-}
-
-impl ResolveResult {
- pub fn matched_entry(&self) -> Option<&RefEntry> {
- match self {
- Self::Match(entry) => Some(entry),
- _ => None,
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::{RefMap, ResolveResult, SelectorQuery};
- use crate::backend::BackendWindow;
-
- fn sample_window(native_id: u32, title: &str) -> BackendWindow {
- BackendWindow {
- native_id,
- title: title.to_string(),
- app_name: "TestApp".to_string(),
- x: 10,
- y: 20,
- width: 300,
- height: 200,
- focused: native_id == 1,
- minimized: false,
- }
- }
-
- #[test]
- fn rebuild_assigns_stable_window_ids_for_same_native_window() {
- let mut refs = RefMap::new();
- let first = refs.rebuild(&[sample_window(1, "First")]);
- let second = refs.rebuild(&[sample_window(1, "First Updated")]);
-
- assert_eq!(first[0].window_id, second[0].window_id);
- assert_eq!(second[0].ref_id, "w1");
- }
-
- #[test]
- fn resolve_accepts_ref_and_window_id() {
- let mut refs = RefMap::new();
- let public = refs.rebuild(&[sample_window(42, "Editor")]);
- let window_id = public[0].window_id.clone();
-
- match refs.resolve("@w1") {
- ResolveResult::Match(entry) => assert_eq!(entry.window_id, window_id),
- other => panic!("unexpected resolve result: {other:?}"),
- }
- match refs.resolve(&window_id) {
- ResolveResult::Match(entry) => assert_eq!(entry.backend_window_id, 42),
- other => panic!("unexpected resolve result: {other:?}"),
- }
- match refs.resolve(&format!("id={window_id}")) {
- ResolveResult::Match(entry) => assert_eq!(entry.title, "Editor"),
- other => panic!("unexpected resolve result: {other:?}"),
- }
- }
-
- #[test]
- fn resolve_to_center_uses_window_geometry() {
- let mut refs = RefMap::new();
- refs.rebuild(&[sample_window(7, "Browser")]);
-
- match refs.resolve_to_center("w1") {
- ResolveResult::Match(entry) => assert_eq!(entry.center(), (160, 120)),
- other => panic!("unexpected resolve result: {other:?}"),
- }
- }
-
- #[test]
- fn selector_query_parses_explicit_modes() {
- assert_eq!(
- SelectorQuery::parse("@w1"),
- SelectorQuery::Ref("w1".to_string())
- );
- assert_eq!(
- SelectorQuery::parse("ref=w2"),
- SelectorQuery::Ref("w2".to_string())
- );
- assert_eq!(
- SelectorQuery::parse("id=win4"),
- SelectorQuery::WindowId("win4".to_string())
- );
- assert_eq!(
- SelectorQuery::parse("title=Chromium"),
- SelectorQuery::Title("Chromium".to_string())
- );
- assert_eq!(
- SelectorQuery::parse("class=Navigator"),
- SelectorQuery::Class("Navigator".to_string())
- );
- assert_eq!(SelectorQuery::parse("focused"), SelectorQuery::Focused);
- }
-
- #[test]
- fn resolve_supports_exact_title_class_and_focused_modes() {
- let mut refs = RefMap::new();
- refs.rebuild(&[
- sample_window(1, "Browser"),
- BackendWindow {
- native_id: 2,
- title: "Editor".to_string(),
- app_name: "Code".to_string(),
- x: 0,
- y: 0,
- width: 10,
- height: 10,
- focused: false,
- minimized: false,
- },
- ]);
-
- match refs.resolve("focused") {
- ResolveResult::Match(entry) => assert_eq!(entry.title, "Browser"),
- other => panic!("unexpected resolve result: {other:?}"),
- }
- match refs.resolve("title=Editor") {
- ResolveResult::Match(entry) => assert_eq!(entry.app_class, "Code"),
- other => panic!("unexpected resolve result: {other:?}"),
- }
- match refs.resolve("class=code") {
- ResolveResult::Match(entry) => assert_eq!(entry.title, "Editor"),
- other => panic!("unexpected resolve result: {other:?}"),
- }
- }
-
- #[test]
- fn fuzzy_resolution_fails_with_candidates_when_ambiguous() {
- let mut refs = RefMap::new();
- refs.rebuild(&[
- sample_window(1, "Chromium"),
- BackendWindow {
- native_id: 2,
- title: "Chromium Settings".to_string(),
- app_name: "Chromium".to_string(),
- x: 0,
- y: 0,
- width: 10,
- height: 10,
- focused: false,
- minimized: false,
- },
- ]);
-
- match refs.resolve("chromium") {
- ResolveResult::Ambiguous {
- mode, candidates, ..
- } => {
- assert_eq!(mode, "fuzzy");
- assert_eq!(candidates.len(), 2);
- }
- other => panic!("unexpected resolve result: {other:?}"),
- }
+ self.map.iter()
}
}
diff --git a/src/core/session.rs b/src/core/session.rs
index 6a2b6bd..68ee0d0 100644
--- a/src/core/session.rs
+++ b/src/core/session.rs
@@ -15,14 +15,14 @@ pub fn detect_session() -> Result {
bail!(
"No X11 session detected.\n\
XDG_SESSION_TYPE is not set and DISPLAY is not set.\n\
- deskctl requires an X11 session."
+ deskctl requires an X11 session. Wayland support coming in v0.2."
);
}
}
"wayland" => {
bail!(
"Wayland session detected (XDG_SESSION_TYPE=wayland).\n\
- deskctl currently supports X11 only."
+ deskctl currently supports X11 only. Wayland/Hyprland support coming in v0.2."
);
}
other => {
diff --git a/src/core/types.rs b/src/core/types.rs
index 0dca365..3c6d36b 100644
--- a/src/core/types.rs
+++ b/src/core/types.rs
@@ -8,10 +8,10 @@ pub struct Snapshot {
}
#[allow(dead_code)]
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
pub struct WindowInfo {
pub ref_id: String,
- pub window_id: String,
+ pub xcb_id: u32,
pub title: String,
pub app_name: String,
pub x: i32,
@@ -22,47 +22,6 @@ pub struct WindowInfo {
pub minimized: bool,
}
-#[allow(dead_code)]
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct MonitorInfo {
- pub name: String,
- pub x: i32,
- pub y: i32,
- pub width: u32,
- pub height: u32,
- pub width_mm: u32,
- pub height_mm: u32,
- pub primary: bool,
- pub automatic: bool,
-}
-
-#[allow(dead_code)]
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct ScreenSize {
- pub width: u32,
- pub height: u32,
-}
-
-#[allow(dead_code)]
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct VersionInfo {
- pub version: String,
- pub backend: String,
-}
-
-#[allow(dead_code)]
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct SystemInfo {
- pub backend: String,
- pub display: Option,
- pub session_type: Option,
- pub session: String,
- pub socket_path: String,
- pub screen: ScreenSize,
- pub monitor_count: usize,
- pub monitors: Vec,
-}
-
impl std::fmt::Display for WindowInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let state = if self.focused {
@@ -88,21 +47,9 @@ impl std::fmt::Display for WindowInfo {
#[allow(dead_code)]
fn truncate(s: &str, max: usize) -> String {
- if s.chars().count() <= max {
+ if s.len() <= max {
s.to_string()
} else {
- let truncated: String = s.chars().take(max.saturating_sub(3)).collect();
- format!("{truncated}...")
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::truncate;
-
- #[test]
- fn truncate_is_char_safe() {
- let input = format!("fire{}fox", '\u{00E9}');
- assert_eq!(truncate(&input, 7), "fire...");
+ format!("{}...", &s[..max - 3])
}
}
diff --git a/src/daemon/handler.rs b/src/daemon/handler.rs
index e8cab3a..5a3fc2e 100644
--- a/src/daemon/handler.rs
+++ b/src/daemon/handler.rs
@@ -1,18 +1,13 @@
use std::sync::Arc;
-
-use anyhow::{Context, Result};
use tokio::sync::Mutex;
-use tokio::time::{sleep, Duration, Instant};
use super::state::DaemonState;
-use crate::backend::annotate::annotate_screenshot;
+use crate::backend::DesktopBackend;
use crate::core::protocol::{Request, Response};
-use crate::core::refs::{ResolveResult, SelectorQuery};
-use crate::core::types::{MonitorInfo, ScreenSize, Snapshot, SystemInfo, VersionInfo, WindowInfo};
+use crate::core::refs::RefEntry;
pub async fn handle_request(request: &Request, state: &Arc>) -> Response {
match request.action.as_str() {
- "ping" => Response::ok(serde_json::json!({"message": "pong"})),
"snapshot" => handle_snapshot(request, state).await,
"click" => handle_click(request, state).await,
"dblclick" => handle_dblclick(request, state).await,
@@ -29,12 +24,6 @@ pub async fn handle_request(request: &Request, state: &Arc>)
"list-windows" => handle_list_windows(state).await,
"get-screen-size" => handle_get_screen_size(state).await,
"get-mouse-position" => handle_get_mouse_position(state).await,
- "get-active-window" => handle_get_active_window(state).await,
- "get-monitors" => handle_get_monitors(state).await,
- "get-version" => handle_get_version(state).await,
- "get-systeminfo" => handle_get_systeminfo(state).await,
- "wait-window" => handle_wait(request, state, WaitKind::Window).await,
- "wait-focus" => handle_wait(request, state, WaitKind::Focus).await,
"screenshot" => handle_screenshot(request, state).await,
"launch" => handle_launch(request, state).await,
action => Response::err(format!("Unknown action: {action}")),
@@ -49,154 +38,152 @@ async fn handle_snapshot(request: &Request, state: &Arc>) ->
.unwrap_or(false);
let mut state = state.lock().await;
- match capture_snapshot(&mut state, annotate, None) {
- Ok(snapshot) => Response::ok(serde_json::to_value(&snapshot).unwrap_or_default()),
- Err(error) => Response::err(format!("Snapshot failed: {error}")),
+
+ match state.backend.snapshot(annotate) {
+ Ok(snapshot) => {
+ // Update ref map
+ state.ref_map.clear();
+ for win in &snapshot.windows {
+ state.ref_map.insert(RefEntry {
+ xcb_id: win.xcb_id,
+ app_class: win.app_name.clone(),
+ title: win.title.clone(),
+ pid: 0, // xcap doesn't expose PID directly in snapshot
+ x: win.x,
+ y: win.y,
+ width: win.width,
+ height: win.height,
+ focused: win.focused,
+ minimized: win.minimized,
+ });
+ }
+
+ Response::ok(serde_json::to_value(&snapshot).unwrap_or_default())
+ }
+ Err(e) => Response::err(format!("Snapshot failed: {e}")),
}
}
async fn handle_click(request: &Request, state: &Arc>) -> Response {
let selector = match request.extra.get("selector").and_then(|v| v.as_str()) {
- Some(selector) => selector.to_string(),
+ Some(s) => s.to_string(),
None => return Response::err("Missing 'selector' field"),
};
let mut state = state.lock().await;
- let selector_query = SelectorQuery::parse(&selector);
+ // Try to parse as coordinates "x,y"
if let Some((x, y)) = parse_coords(&selector) {
return match state.backend.click(x, y) {
Ok(()) => Response::ok(serde_json::json!({"clicked": {"x": x, "y": y}})),
- Err(error) => Response::err(format!("Click failed: {error}")),
+ Err(e) => Response::err(format!("Click failed: {e}")),
};
}
- if selector_query.needs_live_refresh() {
- if let Err(error) = refresh_windows(&mut state) {
- return Response::err(format!("Click failed: {error}"));
- }
- }
-
+ // Resolve as window ref
match state.ref_map.resolve_to_center(&selector) {
- ResolveResult::Match(entry) => {
- let (x, y) = entry.center();
- match state.backend.click(x, y) {
- Ok(()) => Response::ok(serde_json::json!({
- "clicked": {"x": x, "y": y},
- "selector": selector,
- "ref_id": entry.ref_id,
- "window_id": entry.window_id,
- "title": entry.title,
- })),
- Err(error) => Response::err(format!("Click failed: {error}")),
+ Some((x, y)) => match state.backend.click(x, y) {
+ Ok(()) => {
+ Response::ok(serde_json::json!({"clicked": {"x": x, "y": y, "ref": selector}}))
}
- }
- outcome => selector_failure_response(outcome),
+ Err(e) => Response::err(format!("Click failed: {e}")),
+ },
+ None => Response::err(format!("Could not resolve selector: {selector}")),
}
}
async fn handle_dblclick(request: &Request, state: &Arc>) -> Response {
let selector = match request.extra.get("selector").and_then(|v| v.as_str()) {
- Some(selector) => selector.to_string(),
+ Some(s) => s.to_string(),
None => return Response::err("Missing 'selector' field"),
};
let mut state = state.lock().await;
- let selector_query = SelectorQuery::parse(&selector);
if let Some((x, y)) = parse_coords(&selector) {
return match state.backend.dblclick(x, y) {
Ok(()) => Response::ok(serde_json::json!({"double_clicked": {"x": x, "y": y}})),
- Err(error) => Response::err(format!("Double-click failed: {error}")),
+ Err(e) => Response::err(format!("Double-click failed: {e}")),
};
}
- if selector_query.needs_live_refresh() {
- if let Err(error) = refresh_windows(&mut state) {
- return Response::err(format!("Double-click failed: {error}"));
- }
- }
-
match state.ref_map.resolve_to_center(&selector) {
- ResolveResult::Match(entry) => {
- let (x, y) = entry.center();
- match state.backend.dblclick(x, y) {
- Ok(()) => Response::ok(serde_json::json!({
- "double_clicked": {"x": x, "y": y},
- "selector": selector,
- "ref_id": entry.ref_id,
- "window_id": entry.window_id,
- "title": entry.title,
- })),
- Err(error) => Response::err(format!("Double-click failed: {error}")),
- }
- }
- outcome => selector_failure_response(outcome),
+ Some((x, y)) => match state.backend.dblclick(x, y) {
+ Ok(()) => Response::ok(
+ serde_json::json!({"double_clicked": {"x": x, "y": y, "ref": selector}}),
+ ),
+ Err(e) => Response::err(format!("Double-click failed: {e}")),
+ },
+ None => Response::err(format!("Could not resolve selector: {selector}")),
}
}
async fn handle_type(request: &Request, state: &Arc>) -> Response {
let text = match request.extra.get("text").and_then(|v| v.as_str()) {
- Some(text) => text.to_string(),
+ Some(t) => t.to_string(),
None => return Response::err("Missing 'text' field"),
};
let mut state = state.lock().await;
+
match state.backend.type_text(&text) {
Ok(()) => Response::ok(serde_json::json!({"typed": text})),
- Err(error) => Response::err(format!("Type failed: {error}")),
+ Err(e) => Response::err(format!("Type failed: {e}")),
}
}
async fn handle_press(request: &Request, state: &Arc>) -> Response {
let key = match request.extra.get("key").and_then(|v| v.as_str()) {
- Some(key) => key.to_string(),
+ Some(k) => k.to_string(),
None => return Response::err("Missing 'key' field"),
};
let mut state = state.lock().await;
+
match state.backend.press_key(&key) {
Ok(()) => Response::ok(serde_json::json!({"pressed": key})),
- Err(error) => Response::err(format!("Key press failed: {error}")),
+ Err(e) => Response::err(format!("Key press failed: {e}")),
}
}
async fn handle_hotkey(request: &Request, state: &Arc>) -> Response {
let keys: Vec = match request.extra.get("keys").and_then(|v| v.as_array()) {
- Some(keys) => keys
+ Some(arr) => arr
.iter()
- .filter_map(|value| value.as_str().map(|s| s.to_string()))
+ .filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect(),
None => return Response::err("Missing 'keys' field"),
};
let mut state = state.lock().await;
+
match state.backend.hotkey(&keys) {
Ok(()) => Response::ok(serde_json::json!({"hotkey": keys})),
- Err(error) => Response::err(format!("Hotkey failed: {error}")),
+ Err(e) => Response::err(format!("Hotkey failed: {e}")),
}
}
async fn handle_mouse_move(request: &Request, state: &Arc>) -> Response {
let x = match request.extra.get("x").and_then(|v| v.as_i64()) {
- Some(value) => value as i32,
+ Some(v) => v as i32,
None => return Response::err("Missing 'x' field"),
};
let y = match request.extra.get("y").and_then(|v| v.as_i64()) {
- Some(value) => value as i32,
+ Some(v) => v as i32,
None => return Response::err("Missing 'y' field"),
};
let mut state = state.lock().await;
+
match state.backend.mouse_move(x, y) {
Ok(()) => Response::ok(serde_json::json!({"moved": {"x": x, "y": y}})),
- Err(error) => Response::err(format!("Mouse move failed: {error}")),
+ Err(e) => Response::err(format!("Mouse move failed: {e}")),
}
}
async fn handle_mouse_scroll(request: &Request, state: &Arc>) -> Response {
let amount = match request.extra.get("amount").and_then(|v| v.as_i64()) {
- Some(value) => value as i32,
+ Some(v) => v as i32,
None => return Response::err("Missing 'amount' field"),
};
let axis = request
@@ -207,31 +194,33 @@ async fn handle_mouse_scroll(request: &Request, state: &Arc>)
.to_string();
let mut state = state.lock().await;
+
match state.backend.scroll(amount, &axis) {
Ok(()) => Response::ok(serde_json::json!({"scrolled": {"amount": amount, "axis": axis}})),
- Err(error) => Response::err(format!("Scroll failed: {error}")),
+ Err(e) => Response::err(format!("Scroll failed: {e}")),
}
}
async fn handle_mouse_drag(request: &Request, state: &Arc>) -> Response {
let x1 = match request.extra.get("x1").and_then(|v| v.as_i64()) {
- Some(value) => value as i32,
+ Some(v) => v as i32,
None => return Response::err("Missing 'x1' field"),
};
let y1 = match request.extra.get("y1").and_then(|v| v.as_i64()) {
- Some(value) => value as i32,
+ Some(v) => v as i32,
None => return Response::err("Missing 'y1' field"),
};
let x2 = match request.extra.get("x2").and_then(|v| v.as_i64()) {
- Some(value) => value as i32,
+ Some(v) => v as i32,
None => return Response::err("Missing 'x2' field"),
};
let y2 = match request.extra.get("y2").and_then(|v| v.as_i64()) {
- Some(value) => value as i32,
+ Some(v) => v as i32,
None => return Response::err("Missing 'y2' field"),
};
let mut state = state.lock().await;
+
match state.backend.drag(x1, y1, x2, y2) {
Ok(()) => Response::ok(serde_json::json!({
"dragged": {
@@ -239,7 +228,7 @@ async fn handle_mouse_drag(request: &Request, state: &Arc>) -
"to": {"x": x2, "y": y2}
}
})),
- Err(error) => Response::err(format!("Drag failed: {error}")),
+ Err(e) => Response::err(format!("Drag failed: {e}")),
}
}
@@ -249,25 +238,20 @@ async fn handle_window_action(
action: &str,
) -> Response {
let selector = match request.extra.get("selector").and_then(|v| v.as_str()) {
- Some(selector) => selector.to_string(),
+ Some(s) => s.to_string(),
None => return Response::err("Missing 'selector' field"),
};
let mut state = state.lock().await;
- let selector_query = SelectorQuery::parse(&selector);
- if selector_query.needs_live_refresh() {
- if let Err(error) = refresh_windows(&mut state) {
- return Response::err(format!("{action} failed: {error}"));
- }
- }
+
let entry = match state.ref_map.resolve(&selector) {
- ResolveResult::Match(entry) => entry,
- outcome => return selector_failure_response(outcome),
+ Some(e) => e.clone(),
+ None => return Response::err(format!("Could not resolve window: {selector}")),
};
let result = match action {
- "focus" => state.backend.focus_window(entry.backend_window_id),
- "close" => state.backend.close_window(entry.backend_window_id),
+ "focus" => state.backend.focus_window(entry.xcb_id),
+ "close" => state.backend.close_window(entry.xcb_id),
_ => unreachable!(),
};
@@ -275,107 +259,96 @@ async fn handle_window_action(
Ok(()) => Response::ok(serde_json::json!({
"action": action,
"window": entry.title,
- "title": entry.title,
- "ref_id": entry.ref_id,
- "window_id": entry.window_id,
- "selector": selector,
+ "xcb_id": entry.xcb_id,
})),
- Err(error) => Response::err(format!("{action} failed: {error}")),
+ Err(e) => Response::err(format!("{action} failed: {e}")),
}
}
async fn handle_move_window(request: &Request, state: &Arc>) -> Response {
let selector = match request.extra.get("selector").and_then(|v| v.as_str()) {
- Some(selector) => selector.to_string(),
+ Some(s) => s.to_string(),
None => return Response::err("Missing 'selector' field"),
};
let x = request.extra.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
let y = request.extra.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
let mut state = state.lock().await;
- let selector_query = SelectorQuery::parse(&selector);
- if selector_query.needs_live_refresh() {
- if let Err(error) = refresh_windows(&mut state) {
- return Response::err(format!("Move failed: {error}"));
- }
- }
let entry = match state.ref_map.resolve(&selector) {
- ResolveResult::Match(entry) => entry,
- outcome => return selector_failure_response(outcome),
+ Some(e) => e.clone(),
+ None => return Response::err(format!("Could not resolve window: {selector}")),
};
- match state.backend.move_window(entry.backend_window_id, x, y) {
+ match state.backend.move_window(entry.xcb_id, x, y) {
Ok(()) => Response::ok(serde_json::json!({
- "moved": entry.title,
- "title": entry.title,
- "ref_id": entry.ref_id,
- "window_id": entry.window_id,
- "selector": selector,
- "x": x,
- "y": y,
+ "moved": entry.title, "x": x, "y": y
})),
- Err(error) => Response::err(format!("Move failed: {error}")),
+ Err(e) => Response::err(format!("Move failed: {e}")),
}
}
async fn handle_resize_window(request: &Request, state: &Arc>) -> Response {
let selector = match request.extra.get("selector").and_then(|v| v.as_str()) {
- Some(selector) => selector.to_string(),
+ Some(s) => s.to_string(),
None => return Response::err("Missing 'selector' field"),
};
- let width = request
+ let w = request
.extra
.get("w")
.and_then(|v| v.as_u64())
.unwrap_or(800) as u32;
- let height = request
+ let h = request
.extra
.get("h")
.and_then(|v| v.as_u64())
.unwrap_or(600) as u32;
let mut state = state.lock().await;
- let selector_query = SelectorQuery::parse(&selector);
- if selector_query.needs_live_refresh() {
- if let Err(error) = refresh_windows(&mut state) {
- return Response::err(format!("Resize failed: {error}"));
- }
- }
let entry = match state.ref_map.resolve(&selector) {
- ResolveResult::Match(entry) => entry,
- outcome => return selector_failure_response(outcome),
+ Some(e) => e.clone(),
+ None => return Response::err(format!("Could not resolve window: {selector}")),
};
- match state
- .backend
- .resize_window(entry.backend_window_id, width, height)
- {
+ match state.backend.resize_window(entry.xcb_id, w, h) {
Ok(()) => Response::ok(serde_json::json!({
- "resized": entry.title,
- "title": entry.title,
- "ref_id": entry.ref_id,
- "window_id": entry.window_id,
- "selector": selector,
- "width": width,
- "height": height,
+ "resized": entry.title, "width": w, "height": h
})),
- Err(error) => Response::err(format!("Resize failed: {error}")),
+ Err(e) => Response::err(format!("Resize failed: {e}")),
}
}
async fn handle_list_windows(state: &Arc>) -> Response {
let mut state = state.lock().await;
- match refresh_windows(&mut state) {
- Ok(windows) => Response::ok(serde_json::json!({"windows": windows})),
- Err(error) => Response::err(format!("List windows failed: {error}")),
+ // Re-run snapshot without screenshot, just to get current window list
+ match state.backend.snapshot(false) {
+ Ok(snapshot) => {
+ // Update ref map with fresh data
+ state.ref_map.clear();
+ for win in &snapshot.windows {
+ state.ref_map.insert(RefEntry {
+ xcb_id: win.xcb_id,
+ app_class: win.app_name.clone(),
+ title: win.title.clone(),
+ pid: 0,
+ x: win.x,
+ y: win.y,
+ width: win.width,
+ height: win.height,
+ focused: win.focused,
+ minimized: win.minimized,
+ });
+ }
+ Response::ok(serde_json::json!({"windows": snapshot.windows}))
+ }
+ Err(e) => Response::err(format!("List windows failed: {e}")),
}
}
async fn handle_get_screen_size(state: &Arc>) -> Response {
let state = state.lock().await;
match state.backend.screen_size() {
- Ok((width, height)) => Response::ok(serde_json::json!({"width": width, "height": height})),
- Err(error) => Response::err(format!("Failed: {error}")),
+ Ok((w, h)) => Response::ok(serde_json::json!({"width": w, "height": h})),
+ Err(e) => Response::err(format!("Failed: {e}")),
}
}
@@ -383,189 +356,10 @@ async fn handle_get_mouse_position(state: &Arc>) -> Response
let state = state.lock().await;
match state.backend.mouse_position() {
Ok((x, y)) => Response::ok(serde_json::json!({"x": x, "y": y})),
- Err(error) => Response::err(format!("Failed: {error}")),
+ Err(e) => Response::err(format!("Failed: {e}")),
}
}
-async fn handle_get_active_window(state: &Arc>) -> Response {
- let mut state = state.lock().await;
- let active_backend_window = match state.backend.active_window() {
- Ok(window) => window,
- Err(error) => return Response::err(format!("Failed: {error}")),
- };
-
- let windows = match refresh_windows(&mut state) {
- Ok(windows) => windows,
- Err(error) => return Response::err(format!("Failed: {error}")),
- };
-
- let active_window = if let Some(active_backend_window) = active_backend_window {
- state
- .ref_map
- .entries()
- .find_map(|(_, entry)| {
- (entry.backend_window_id == active_backend_window.native_id)
- .then(|| entry.to_window_info())
- })
- .or_else(|| windows.iter().find(|window| window.focused).cloned())
- } else {
- windows.iter().find(|window| window.focused).cloned()
- };
-
- if let Some(window) = active_window {
- Response::ok(serde_json::json!({"window": window}))
- } else {
- Response::err_with_data(
- "No focused window is available",
- serde_json::json!({"kind": "not_found", "mode": "focused"}),
- )
- }
-}
-
-async fn handle_get_monitors(state: &Arc>) -> Response {
- let state = state.lock().await;
- match state.backend.list_monitors() {
- Ok(monitors) => {
- let monitors: Vec = monitors.into_iter().map(Into::into).collect();
- Response::ok(serde_json::json!({
- "count": monitors.len(),
- "monitors": monitors,
- }))
- }
- Err(error) => Response::err(format!("Failed: {error}")),
- }
-}
-
-async fn handle_get_version(state: &Arc>) -> Response {
- let state = state.lock().await;
- let info = VersionInfo {
- version: env!("CARGO_PKG_VERSION").to_string(),
- backend: state.backend.backend_name().to_string(),
- };
- Response::ok(serde_json::to_value(info).unwrap_or_default())
-}
-
-async fn handle_get_systeminfo(state: &Arc>) -> Response {
- let state = state.lock().await;
- let screen = match state.backend.screen_size() {
- Ok((width, height)) => ScreenSize { width, height },
- Err(error) => return Response::err(format!("Failed: {error}")),
- };
- let monitors = match state.backend.list_monitors() {
- Ok(monitors) => monitors.into_iter().map(Into::into).collect::>(),
- Err(error) => return Response::err(format!("Failed: {error}")),
- };
-
- let info = SystemInfo {
- backend: state.backend.backend_name().to_string(),
- display: std::env::var("DISPLAY")
- .ok()
- .filter(|value| !value.is_empty()),
- session_type: std::env::var("XDG_SESSION_TYPE")
- .ok()
- .filter(|value| !value.is_empty()),
- session: state.session.clone(),
- socket_path: state.socket_path.display().to_string(),
- screen,
- monitor_count: monitors.len(),
- monitors,
- };
-
- Response::ok(serde_json::to_value(info).unwrap_or_default())
-}
-
-async fn handle_wait(
- request: &Request,
- state: &Arc>,
- wait_kind: WaitKind,
-) -> Response {
- let selector = match request.extra.get("selector").and_then(|v| v.as_str()) {
- Some(selector) => selector.to_string(),
- None => return Response::err("Missing 'selector' field"),
- };
- let timeout_ms = request
- .extra
- .get("timeout_ms")
- .and_then(|v| v.as_u64())
- .unwrap_or(10_000);
- let poll_ms = request
- .extra
- .get("poll_ms")
- .and_then(|v| v.as_u64())
- .unwrap_or(250);
-
- let start = Instant::now();
- let deadline = Instant::now() + Duration::from_millis(timeout_ms);
- let mut last_observation: serde_json::Value;
-
- loop {
- let outcome = {
- let mut state = state.lock().await;
- if let Err(error) = refresh_windows(&mut state) {
- return Response::err(format!("Wait failed: {error}"));
- }
- observe_wait(&state, &selector, wait_kind)
- };
-
- match outcome {
- WaitObservation::Satisfied(window) => {
- let elapsed_ms = start.elapsed().as_millis() as u64;
- return Response::ok(serde_json::json!({
- "wait": wait_kind.as_str(),
- "selector": selector,
- "elapsed_ms": elapsed_ms,
- "window": window,
- }));
- }
- WaitObservation::Retry { observation } => {
- last_observation = observation;
- }
- WaitObservation::Failure(response) => return response,
- }
-
- if Instant::now() >= deadline {
- return Response::err_with_data(
- format!(
- "Timed out waiting for {} to match selector: {}",
- wait_kind.as_str(),
- selector
- ),
- serde_json::json!({
- "kind": "timeout",
- "wait": wait_kind.as_str(),
- "selector": selector,
- "timeout_ms": timeout_ms,
- "poll_ms": poll_ms,
- "last_observation": last_observation,
- }),
- );
- }
-
- sleep(Duration::from_millis(poll_ms)).await;
- }
-}
-
-#[derive(Clone, Copy)]
-enum WaitKind {
- Window,
- Focus,
-}
-
-impl WaitKind {
- fn as_str(self) -> &'static str {
- match self {
- Self::Window => "window",
- Self::Focus => "focus",
- }
- }
-}
-
-enum WaitObservation {
- Satisfied(WindowInfo),
- Retry { observation: serde_json::Value },
- Failure(Response),
-}
-
async fn handle_screenshot(request: &Request, state: &Arc>) -> Response {
let annotate = request
.extra
@@ -576,214 +370,50 @@ async fn handle_screenshot(request: &Request, state: &Arc>) -
.extra
.get("path")
.and_then(|v| v.as_str())
- .map(|value| value.to_string())
- .unwrap_or_else(temp_screenshot_path);
-
+ .map(|s| s.to_string())
+ .unwrap_or_else(|| {
+ let ts = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_millis();
+ format!("/tmp/deskctl-{ts}.png")
+ });
let mut state = state.lock().await;
- let windows = if annotate {
- match refresh_windows(&mut state) {
- Ok(windows) => Some(windows),
- Err(error) => return Response::err(format!("Screenshot failed: {error}")),
- }
- } else {
- None
- };
-
- match capture_and_save_screenshot(&mut state, &path, annotate, windows.as_deref()) {
- Ok(saved) => {
- if let Some(windows) = windows {
- Response::ok(serde_json::json!({"screenshot": saved, "windows": windows}))
- } else {
- Response::ok(serde_json::json!({"screenshot": saved}))
- }
- }
- Err(error) => Response::err(format!("Screenshot failed: {error}")),
+ match state.backend.screenshot(&path, annotate) {
+ Ok(saved) => Response::ok(serde_json::json!({"screenshot": saved})),
+ Err(e) => Response::err(format!("Screenshot failed: {e}")),
}
}
async fn handle_launch(request: &Request, state: &Arc>) -> Response {
let command = match request.extra.get("command").and_then(|v| v.as_str()) {
- Some(command) => command.to_string(),
+ Some(c) => c.to_string(),
None => return Response::err("Missing 'command' field"),
};
let args: Vec = request
.extra
.get("args")
.and_then(|v| v.as_array())
- .map(|args| {
- args.iter()
- .filter_map(|value| value.as_str().map(String::from))
+ .map(|arr| {
+ arr.iter()
+ .filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
-
let state = state.lock().await;
match state.backend.launch(&command, &args) {
Ok(pid) => Response::ok(serde_json::json!({"pid": pid, "command": command})),
- Err(error) => Response::err(format!("Launch failed: {error}")),
+ Err(e) => Response::err(format!("Launch failed: {e}")),
}
}
-fn refresh_windows(state: &mut DaemonState) -> Result> {
- let windows = state.backend.list_windows()?;
- Ok(state.ref_map.rebuild(&windows))
-}
-
-fn selector_failure_response(result: ResolveResult) -> Response {
- match result {
- ResolveResult::NotFound { selector, mode } => Response::err_with_data(
- format!("Could not resolve selector: {selector}"),
- serde_json::json!({
- "kind": "selector_not_found",
- "selector": selector,
- "mode": mode,
- }),
- ),
- ResolveResult::Ambiguous {
- selector,
- mode,
- candidates,
- } => Response::err_with_data(
- format!("Selector is ambiguous: {selector}"),
- serde_json::json!({
- "kind": "selector_ambiguous",
- "selector": selector,
- "mode": mode,
- "candidates": candidates,
- }),
- ),
- ResolveResult::Invalid {
- selector,
- mode,
- message,
- } => Response::err_with_data(
- format!("Invalid selector '{selector}': {message}"),
- serde_json::json!({
- "kind": "selector_invalid",
- "selector": selector,
- "mode": mode,
- "message": message,
- }),
- ),
- ResolveResult::Match(_) => unreachable!(),
- }
-}
-
-fn observe_wait(state: &DaemonState, selector: &str, wait_kind: WaitKind) -> WaitObservation {
- match state.ref_map.resolve(selector) {
- ResolveResult::Match(entry) => {
- let window = entry.to_window_info();
- match wait_kind {
- WaitKind::Window => WaitObservation::Satisfied(window),
- WaitKind::Focus if window.focused => WaitObservation::Satisfied(window),
- WaitKind::Focus => WaitObservation::Retry {
- observation: serde_json::json!({
- "kind": "window_not_focused",
- "window": window,
- }),
- },
- }
- }
- ResolveResult::NotFound { selector, mode } => WaitObservation::Retry {
- observation: serde_json::json!({
- "kind": "selector_not_found",
- "selector": selector,
- "mode": mode,
- }),
- },
- ResolveResult::Ambiguous {
- selector,
- mode,
- candidates,
- } => WaitObservation::Failure(Response::err_with_data(
- format!("Selector is ambiguous: {selector}"),
- serde_json::json!({
- "kind": "selector_ambiguous",
- "selector": selector,
- "mode": mode,
- "candidates": candidates,
- }),
- )),
- ResolveResult::Invalid {
- selector,
- mode,
- message,
- } => WaitObservation::Failure(Response::err_with_data(
- format!("Invalid selector '{selector}': {message}"),
- serde_json::json!({
- "kind": "selector_invalid",
- "selector": selector,
- "mode": mode,
- "message": message,
- }),
- )),
- }
-}
-
-fn capture_snapshot(
- state: &mut DaemonState,
- annotate: bool,
- path: Option,
-) -> 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))?;
-
- Ok(Snapshot {
- screenshot,
- windows,
- })
-}
-
-fn capture_and_save_screenshot(
- state: &mut DaemonState,
- path: &str,
- annotate: bool,
- windows: Option<&[WindowInfo]>,
-) -> Result {
- let mut image = state.backend.capture_screenshot()?;
- if annotate {
- let windows = windows.context("Annotated screenshots require current window data")?;
- annotate_screenshot(&mut image, windows);
- }
- image
- .save(path)
- .with_context(|| format!("Failed to save screenshot to {path}"))?;
- Ok(path.to_string())
-}
-
-fn temp_screenshot_path() -> String {
- let timestamp = std::time::SystemTime::now()
- .duration_since(std::time::UNIX_EPOCH)
- .unwrap_or_default()
- .as_millis();
- format!("/tmp/deskctl-{timestamp}.png")
-}
-
-fn parse_coords(value: &str) -> Option<(i32, i32)> {
- let parts: Vec<&str> = value.split(',').collect();
- if parts.len() != 2 {
- return None;
- }
-
- let x = parts[0].trim().parse().ok()?;
- let y = parts[1].trim().parse().ok()?;
- Some((x, y))
-}
-
-impl From for MonitorInfo {
- fn from(value: crate::backend::BackendMonitor) -> Self {
- Self {
- name: value.name,
- x: value.x,
- y: value.y,
- width: value.width,
- height: value.height,
- width_mm: value.width_mm,
- height_mm: value.height_mm,
- primary: value.primary,
- automatic: value.automatic,
- }
+fn parse_coords(s: &str) -> Option<(i32, i32)> {
+ let parts: Vec<&str> = s.split(',').collect();
+ if parts.len() == 2 {
+ let x = parts[0].trim().parse().ok()?;
+ let y = parts[1].trim().parse().ok()?;
+ Some((x, y))
+ } else {
+ None
}
}
diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs
index 9e7e931..f838bbf 100644
--- a/src/daemon/mod.rs
+++ b/src/daemon/mod.rs
@@ -1,7 +1,7 @@
mod handler;
mod state;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
@@ -9,33 +9,9 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixListener;
use tokio::sync::Mutex;
-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()?;
@@ -48,29 +24,31 @@ 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();
+ let socket_path = std::env::var("DESKCTL_SOCKET_PATH")
+ .map(PathBuf::from)
+ .context("DESKCTL_SOCKET_PATH not set")?;
+
+ let pid_path = std::env::var("DESKCTL_PID_PATH").map(PathBuf::from).ok();
// Clean up stale socket
if socket_path.exists() {
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();
@@ -99,6 +77,14 @@ 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(())
}
@@ -139,11 +125,3 @@ 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/src/daemon/state.rs b/src/daemon/state.rs
index 23b0e3b..57e865c 100644
--- a/src/daemon/state.rs
+++ b/src/daemon/state.rs
@@ -1,6 +1,6 @@
use std::path::PathBuf;
-use crate::backend::{x11::X11Backend, DesktopBackend};
+use crate::backend::x11::X11Backend;
use crate::core::refs::RefMap;
#[allow(dead_code)]
@@ -8,12 +8,12 @@ pub struct DaemonState {
pub session: String,
pub socket_path: PathBuf,
pub ref_map: RefMap,
- pub backend: Box,
+ pub backend: X11Backend,
}
impl DaemonState {
pub fn new(session: String, socket_path: PathBuf) -> anyhow::Result {
- let backend: Box = Box::new(X11Backend::new()?);
+ let backend = X11Backend::new()?;
Ok(Self {
session,
socket_path,
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index 408a4fc..0000000
--- a/src/lib.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-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 ed77595..349b70d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,11 @@
+mod backend;
+mod cli;
+mod core;
+mod daemon;
+
fn main() -> anyhow::Result<()> {
- deskctl::run()
+ if std::env::var("DESKCTL_DAEMON").is_ok() {
+ return daemon::run();
+ }
+ cli::run()
}
diff --git a/tests/support/mod.rs b/tests/support/mod.rs
deleted file mode 100644
index 07cc5a7..0000000
--- a/tests/support/mod.rs
+++ /dev/null
@@ -1,281 +0,0 @@
-#![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::thread;
-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 fn env_lock_guard() -> std::sync::MutexGuard<'static, ()> {
- match env_lock().lock() {
- Ok(guard) => guard,
- Err(poisoned) => poisoned.into_inner(),
- }
-}
-
-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) = connect_to_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 })
- }
-}
-
-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);
- 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 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()))?;
- 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()
- )
- })
- }
-
- 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 {
- fn drop(&mut self) {
- let _ = connection::stop_daemon(&self.opts);
- 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);
- }
-}
-
-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")
-}
-
-pub fn json_response(output: &Output) -> Result {
- 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
deleted file mode 100644
index 30308cb..0000000
--- a/tests/x11_runtime.rs
+++ /dev/null
@@ -1,260 +0,0 @@
-#![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_guard, json_response, successful_json_response,
- FixtureWindow, SessionEnvGuard, TestSession,
-};
-
-#[test]
-fn doctor_reports_healthy_x11_environment() -> Result<()> {
- let _guard = env_lock_guard();
- 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_guard();
- 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_guard();
- 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(())
-}
-
-#[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();
- let Some(_env) = SessionEnvGuard::prepare() else {
- eprintln!("Skipping X11 integration test because DISPLAY is not set");
- return Ok(());
- };
-
- let title = "deskctl wait window test";
- let _window = FixtureWindow::create(title, "DeskctlWait")?;
- let session = TestSession::new("wait-window-success")?;
- let response = successful_json_response(session.run_cli([
- "--json",
- "wait",
- "window",
- "--selector",
- &format!("title={title}"),
- "--timeout",
- "1",
- "--poll-ms",
- "50",
- ])?)?;
-
- let window = response
- .get("data")
- .and_then(|data| data.get("window"))
- .expect("wait window should return a matched window");
- assert_eq!(
- window.get("title").and_then(|value| value.as_str()),
- Some(title)
- );
- assert_eq!(
- response
- .get("data")
- .and_then(|data| data.get("wait"))
- .and_then(|value| value.as_str()),
- Some("window")
- );
-
- Ok(())
-}
-
-#[test]
-fn ambiguous_fuzzy_selector_returns_candidates() -> Result<()> {
- let _guard = env_lock_guard();
- let Some(_env) = SessionEnvGuard::prepare() else {
- eprintln!("Skipping X11 integration test because DISPLAY is not set");
- return Ok(());
- };
-
- let _window_one = FixtureWindow::create("deskctl ambiguity alpha", "DeskctlAmbiguous")?;
- let _window_two = FixtureWindow::create("deskctl ambiguity beta", "DeskctlAmbiguous")?;
- let session = TestSession::new("selector-ambiguity")?;
- let output = session.run_cli(["--json", "focus", "ambiguity"])?;
- let response = json_response(&output)?;
-
- assert!(!output.status.success());
- assert_eq!(
- response.get("success").and_then(|value| value.as_bool()),
- Some(false)
- );
- assert_eq!(
- response
- .get("data")
- .and_then(|data| data.get("kind"))
- .and_then(|value| value.as_str()),
- Some("selector_ambiguous")
- );
- assert!(response
- .get("data")
- .and_then(|data| data.get("candidates"))
- .and_then(|value| value.as_array())
- .map(|candidates| candidates.len() >= 2)
- .unwrap_or(false));
-
- Ok(())
-}
-
-#[test]
-fn wait_focus_timeout_is_structured() -> Result<()> {
- let _guard = env_lock_guard();
- let Some(_env) = SessionEnvGuard::prepare() else {
- eprintln!("Skipping X11 integration test because DISPLAY is not set");
- return Ok(());
- };
-
- let session = TestSession::new("wait-focus-timeout")?;
- let output = session.run_cli([
- "--json",
- "wait",
- "focus",
- "--selector",
- "title=missing-window-for-wait-focus",
- "--timeout",
- "1",
- "--poll-ms",
- "50",
- ])?;
- let response = json_response(&output)?;
-
- assert!(!output.status.success());
- assert_eq!(
- response
- .get("data")
- .and_then(|data| data.get("kind"))
- .and_then(|value| value.as_str()),
- Some("timeout")
- );
- assert_eq!(
- response
- .get("data")
- .and_then(|data| data.get("last_observation"))
- .and_then(|value| value.get("kind"))
- .and_then(|value| value.as_str()),
- Some("selector_not_found")
- );
-
- Ok(())
-}