diff --git a/.github/workflows/desktop-ci.yml b/.github/workflows/desktop-ci.yml new file mode 100644 index 0000000..fa5ab2c --- /dev/null +++ b/.github/workflows/desktop-ci.yml @@ -0,0 +1,93 @@ +# Desktop CI — runs on desktop changes. Verifies the Go bridge, the frontend +# (typecheck + build), and a native macOS app bundle with a bridge smoke test. +name: Desktop CI + +on: + push: + branches: [main] + paths: + - "apps/desktop/**" + - "cmd/neo-bridge/**" + - "internal/**" + - ".github/workflows/desktop-ci.yml" + pull_request: + paths: + - "apps/desktop/**" + - "cmd/neo-bridge/**" + - "internal/**" + - ".github/workflows/desktop-ci.yml" + +concurrency: + group: desktop-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + go: + name: Go (bridge builds + unit tests) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.23" + cache: true + - run: go build ./cmd/neo-bridge + - run: go test ./internal/... ./cmd/neo-bridge/... 2>/dev/null || go test ./... + + frontend: + name: Frontend (typecheck + build) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: apps/desktop/package-lock.json + - working-directory: apps/desktop + run: npm ci + - working-directory: apps/desktop + run: npm run build + + macos-build: + name: macOS build (fmt, clippy, bundle, smoke) + runs-on: macos-14 + timeout-minutes: 40 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: apps/desktop/package-lock.json + - uses: actions/setup-go@v5 + with: + go-version: "1.23" + cache: true + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + targets: aarch64-apple-darwin + - uses: Swatinem/rust-cache@v2 + with: + workspaces: apps/desktop/src-tauri + - name: Build neo-bridge sidecar + run: | + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath \ + -o apps/desktop/src-tauri/binaries/neo-bridge-aarch64-apple-darwin ./cmd/neo-bridge + - working-directory: apps/desktop + run: npm ci + - name: Rust formatting + run: cargo fmt --manifest-path apps/desktop/src-tauri/Cargo.toml --all --check + - name: Rust lint (clippy) + run: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml --all-targets -- -D warnings + - name: Build macOS app bundle (unsigned) + working-directory: apps/desktop + run: npm run tauri build -- --target aarch64-apple-darwin --bundles app + - name: Smoke test bundled bridge + shell: bash + run: | + set -euo pipefail + bridge="apps/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/macos/Neo Desktop.app/Contents/MacOS/neo-bridge" + test -x "$bridge" + "$bridge" bridge.hello | grep -q '"protocolVersion":1' diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml new file mode 100644 index 0000000..62b73de --- /dev/null +++ b/.github/workflows/desktop-release.yml @@ -0,0 +1,175 @@ +# Neo Desktop release pipeline — triggered by `desktop-v*` tags (independent of +# the CLI's `v*` releases). Per target it builds the matching neo-bridge sidecar +# from the tag commit, packages the Tauri app + DMG, smoke-tests the bundled +# bridge, and publishes a draft GitHub Release with the DMGs. +# +# Signing is optional: with no APPLE_CERTIFICATE secret the macOS build is +# unsigned (ad-hoc) instead of failing at `security import`. +name: Desktop Release + +on: + push: + tags: + - "desktop-v*" + +permissions: + contents: write + +concurrency: + group: desktop-release-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + verify: + name: Verify tag and versions + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + version: ${{ steps.meta.outputs.version }} + steps: + - uses: actions/checkout@v4 + - id: meta + shell: bash + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#desktop-v}" + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then + echo "Desktop release tags must be desktop-v; got ${GITHUB_REF_NAME}" >&2 + exit 1 + fi + conf=$(node -p "require('./apps/desktop/src-tauri/tauri.conf.json').version") + pkg=$(node -p "require('./apps/desktop/package.json').version") + if [[ "$conf" != "$version" || "$pkg" != "$version" ]]; then + echo "Version mismatch: tag=$version tauri.conf.json=$conf package.json=$pkg" >&2 + echo "Bump apps/desktop versions before tagging." >&2 + exit 1 + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + + build: + name: Build ${{ matrix.triple }} + needs: verify + strategy: + fail-fast: false + matrix: + include: + - os: macos-14 + triple: aarch64-apple-darwin + goos: darwin + goarch: arm64 + - os: macos-13 + triple: x86_64-apple-darwin + goos: darwin + goarch: amd64 + runs-on: ${{ matrix.os }} + timeout-minutes: 40 + env: + VERSION: ${{ needs.verify.outputs.version }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: apps/desktop/package-lock.json + - uses: actions/setup-go@v5 + with: + go-version: "1.23" + cache: true + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.triple }} + - uses: Swatinem/rust-cache@v2 + with: + workspaces: apps/desktop/src-tauri + + # Build the target-specific bridge sidecar (version-stamped) before the + # Tauri bundle consumes it via externalBin. + - name: Build neo-bridge sidecar + shell: bash + run: | + set -euo pipefail + CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \ + go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \ + -o "apps/desktop/src-tauri/binaries/neo-bridge-${{ matrix.triple }}" ./cmd/neo-bridge + + - name: Install frontend deps + working-directory: apps/desktop + run: npm ci + + - name: Signing preflight + id: signing + shell: bash + env: + HAS_APPLE_CERT: ${{ secrets.APPLE_CERTIFICATE != '' }} + run: | + echo "apple_cert=${HAS_APPLE_CERT}" >> "$GITHUB_OUTPUT" + [ "${HAS_APPLE_CERT}" = "true" ] || echo "::warning::APPLE_CERTIFICATE missing — macOS app will be unsigned" + + # Signed build — only when a certificate secret exists (passing APPLE_* + # env with no cert makes Tauri abort at `security import`). + - name: Build and package (signed) + if: steps.signing.outputs.apple_cert == 'true' + working-directory: apps/desktop + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: npm run tauri build -- --target ${{ matrix.triple }} + + - name: Build and package (unsigned) + if: steps.signing.outputs.apple_cert != 'true' + working-directory: apps/desktop + run: npm run tauri build -- --target ${{ matrix.triple }} + + # The bundled bridge must be executable and report the tag version. + - name: Smoke test bundled bridge + shell: bash + run: | + set -euo pipefail + app="apps/desktop/src-tauri/target/${{ matrix.triple }}/release/bundle/macos/Neo Desktop.app" + bridge="$app/Contents/MacOS/neo-bridge" + test -x "$bridge" || { echo "bundled neo-bridge missing"; exit 1; } + "$bridge" bridge.hello > hello.json + cat hello.json + grep -q '"protocolVersion":1' hello.json + grep -q "\"bridgeVersion\":\"${VERSION}\"" hello.json + + - name: Collect DMG + shell: bash + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/upload" + cp apps/desktop/src-tauri/target/${{ matrix.triple }}/release/bundle/dmg/*.dmg "$RUNNER_TEMP/upload/" + + - uses: actions/upload-artifact@v4 + with: + name: desktop-${{ matrix.triple }} + path: ${{ runner.temp }}/upload/* + + publish: + name: Publish draft release + needs: [verify, build] + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + - name: Create draft release + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + mapfile -t files < <(find dist -type f -name '*.dmg') + echo "Publishing: ${files[*]}" + gh release create "$GITHUB_REF_NAME" \ + --repo "$GITHUB_REPOSITORY" \ + --draft \ + --title "Neo Desktop ${GITHUB_REF_NAME#desktop-v}" \ + --notes "Automated build of the Neo Desktop app (our UI + integrated terminal). Unsigned — right-click the app and choose Open on first launch." \ + "${files[@]}" diff --git a/apps/desktop/.gitignore b/apps/desktop/.gitignore new file mode 100644 index 0000000..b7aecb9 --- /dev/null +++ b/apps/desktop/.gitignore @@ -0,0 +1,32 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Tauri / Rust build output +src-tauri/target +src-tauri/gen + +# Bundled sidecar binaries (generated via `go build` — rebuildable) +src-tauri/binaries/* +!src-tauri/binaries/.gitkeep + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/desktop/.vscode/extensions.json b/apps/desktop/.vscode/extensions.json new file mode 100644 index 0000000..24d7cc6 --- /dev/null +++ b/apps/desktop/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] +} diff --git a/apps/desktop/README.md b/apps/desktop/README.md new file mode 100644 index 0000000..102e366 --- /dev/null +++ b/apps/desktop/README.md @@ -0,0 +1,7 @@ +# Tauri + React + Typescript + +This template should help get you started developing with Tauri, React and Typescript in Vite. + +## Recommended IDE Setup + +- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/apps/desktop/index.html b/apps/desktop/index.html new file mode 100644 index 0000000..ff93803 --- /dev/null +++ b/apps/desktop/index.html @@ -0,0 +1,14 @@ + + + + + + + Tauri + React + Typescript + + + +
+ + + diff --git a/apps/desktop/package-lock.json b/apps/desktop/package-lock.json new file mode 100644 index 0000000..0f959cf --- /dev/null +++ b/apps/desktop/package-lock.json @@ -0,0 +1,2133 @@ +{ + "name": "desktop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "desktop", + "version": "0.1.0", + "dependencies": { + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-opener": "^2", + "@xterm/addon-fit": "^0.11.0", + "@xterm/xterm": "^6.0.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.4.tgz", + "integrity": "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@xterm/addon-fit": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", + "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", + "license": "MIT" + }, + "node_modules/@xterm/xterm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", + "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.393", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", + "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz", + "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 0000000..290a609 --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,28 @@ +{ + "name": "desktop", + "private": true, + "version": "0.2.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-opener": "^2", + "@xterm/addon-fit": "^0.11.0", + "@xterm/xterm": "^6.0.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } +} diff --git a/apps/desktop/public/tauri.svg b/apps/desktop/public/tauri.svg new file mode 100644 index 0000000..31b62c9 --- /dev/null +++ b/apps/desktop/public/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/desktop/public/vite.svg b/apps/desktop/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/apps/desktop/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop/src-tauri/.gitignore b/apps/desktop/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/apps/desktop/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock new file mode 100644 index 0000000..617a1d2 --- /dev/null +++ b/apps/desktop/src-tauri/Cargo.lock @@ -0,0 +1,5116 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.19", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "desktop" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-opener", + "tauri-plugin-positioner", + "tauri-plugin-shell", + "tauri-plugin-single-instance", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.3+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.1", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.19", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "open" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b3d059e795d52b8a72fef45658620edd4d9c359b338564aa14391ffa511ed5" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.19", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.1", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.13.1", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "image", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.19", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.119", + "tauri-utils", + "thiserror 2.0.19", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.19", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-plugin-positioner" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a285cd1dca2bef15e45a591694050773d0beadbe133a55779d5251d08fa2396" +dependencies = [ + "log", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.19", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.19", + "tokio", +] + +[[package]] +name = "tauri-plugin-single-instance" +version = "2.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3214becf9ef5783c0ae99a3bb25adf5353a7a16ebf53e74b909e29205735c6c" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.19", + "tokio", + "tracing", + "windows-sys 0.60.2", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.19", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.19", + "toml 1.1.3+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.3+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.19", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.19", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.19", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zvariant" +version = "5.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.119", + "winnow 1.0.4", +] diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml new file mode 100644 index 0000000..2042aee --- /dev/null +++ b/apps/desktop/src-tauri/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "desktop" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "desktop_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = ["tray-icon", "image-png"] } +tauri-plugin-opener = "2" +tauri-plugin-single-instance = "2" +tauri-plugin-positioner = { version = "2", features = ["tray-icon"] } +tauri-plugin-shell = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + diff --git a/apps/desktop/src-tauri/binaries/.gitkeep b/apps/desktop/src-tauri/binaries/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/apps/desktop/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json new file mode 100644 index 0000000..2acd724 --- /dev/null +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -0,0 +1,31 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the popover and main windows", + "windows": ["popover", "main"], + "permissions": [ + "core:default", + "core:window:allow-start-dragging", + "opener:default", + { + "identifier": "shell:allow-execute", + "allow": [ + { + "name": "binaries/neo-bridge", + "sidecar": true, + "args": true + } + ] + }, + { + "identifier": "shell:allow-spawn", + "allow": [ + { + "name": "binaries/neo-bridge", + "sidecar": true, + "args": true + } + ] + } + ] +} diff --git a/apps/desktop/src-tauri/icons/128x128.png b/apps/desktop/src-tauri/icons/128x128.png new file mode 100644 index 0000000..6be5e50 Binary files /dev/null and b/apps/desktop/src-tauri/icons/128x128.png differ diff --git a/apps/desktop/src-tauri/icons/128x128@2x.png b/apps/desktop/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..e81bece Binary files /dev/null and b/apps/desktop/src-tauri/icons/128x128@2x.png differ diff --git a/apps/desktop/src-tauri/icons/32x32.png b/apps/desktop/src-tauri/icons/32x32.png new file mode 100644 index 0000000..a437dd5 Binary files /dev/null and b/apps/desktop/src-tauri/icons/32x32.png differ diff --git a/apps/desktop/src-tauri/icons/Square107x107Logo.png b/apps/desktop/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..0ca4f27 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square107x107Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square142x142Logo.png b/apps/desktop/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..b81f820 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square142x142Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square150x150Logo.png b/apps/desktop/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..624c7bf Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square150x150Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square284x284Logo.png b/apps/desktop/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..c021d2b Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square284x284Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square30x30Logo.png b/apps/desktop/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..6219700 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square30x30Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square310x310Logo.png b/apps/desktop/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..f9bc048 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square310x310Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square44x44Logo.png b/apps/desktop/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..d5fbfb2 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square44x44Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square71x71Logo.png b/apps/desktop/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..63440d7 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square71x71Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square89x89Logo.png b/apps/desktop/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..f3f705a Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square89x89Logo.png differ diff --git a/apps/desktop/src-tauri/icons/StoreLogo.png b/apps/desktop/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..4556388 Binary files /dev/null and b/apps/desktop/src-tauri/icons/StoreLogo.png differ diff --git a/apps/desktop/src-tauri/icons/icon.icns b/apps/desktop/src-tauri/icons/icon.icns new file mode 100644 index 0000000..12a5bce Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.icns differ diff --git a/apps/desktop/src-tauri/icons/icon.ico b/apps/desktop/src-tauri/icons/icon.ico new file mode 100644 index 0000000..b3636e4 Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.ico differ diff --git a/apps/desktop/src-tauri/icons/icon.png b/apps/desktop/src-tauri/icons/icon.png new file mode 100644 index 0000000..e1cd261 Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.png differ diff --git a/apps/desktop/src-tauri/icons/tray-22.png b/apps/desktop/src-tauri/icons/tray-22.png new file mode 100644 index 0000000..149d52f Binary files /dev/null and b/apps/desktop/src-tauri/icons/tray-22.png differ diff --git a/apps/desktop/src-tauri/icons/tray.png b/apps/desktop/src-tauri/icons/tray.png new file mode 100644 index 0000000..bb45b78 Binary files /dev/null and b/apps/desktop/src-tauri/icons/tray.png differ diff --git a/apps/desktop/src-tauri/icons/tray.svg b/apps/desktop/src-tauri/icons/tray.svg new file mode 100644 index 0000000..8c2407d --- /dev/null +++ b/apps/desktop/src-tauri/icons/tray.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs new file mode 100644 index 0000000..8200530 --- /dev/null +++ b/apps/desktop/src-tauri/src/lib.rs @@ -0,0 +1,141 @@ +mod pty; + +use tauri::{ + tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, + Manager, WindowEvent, +}; +use tauri_plugin_positioner::{Position, WindowExt}; +use tauri_plugin_shell::ShellExt; + +// Proxy a request to the bundled neo-bridge sidecar. The frontend never gets +// generic shell access — only this typed command, which runs a fixed binary. +#[tauri::command] +async fn bridge( + app: tauri::AppHandle, + method: String, + params: Option, +) -> Result { + let mut args = vec![method]; + if let Some(p) = params { + if !p.trim().is_empty() { + args.push(p); + } + } + let output = app + .shell() + .sidecar("neo-bridge") + .map_err(|e| e.to_string())? + .args(args) + .output() + .await + .map_err(|e| e.to_string())?; + + if !output.status.success() { + return Err(String::from_utf8_lossy(&output.stderr).to_string()); + } + Ok(String::from_utf8_lossy(&output.stdout).to_string()) +} + +#[tauri::command] +fn open_dashboard(app: tauri::AppHandle) { + if let Some(w) = app.get_webview_window("main") { + let _ = w.show(); + let _ = w.set_focus(); + } +} + +#[tauri::command] +fn hide_popover(app: tauri::AppHandle) { + if let Some(w) = app.get_webview_window("popover") { + let _ = w.hide(); + } +} + +#[tauri::command] +fn quit_app(app: tauri::AppHandle) { + app.exit(0); +} + +// Position the popover just below the tray icon, then show it. +fn show_popover(app: &tauri::AppHandle) { + if let Some(w) = app.get_webview_window("popover") { + let _ = w.move_window(Position::TrayBottomCenter); + let _ = w.show(); + let _ = w.set_focus(); + } +} + +fn toggle_popover(app: &tauri::AppHandle) { + if let Some(w) = app.get_webview_window("popover") { + if w.is_visible().unwrap_or(false) { + let _ = w.hide(); + } else { + show_popover(app); + } + } +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { + show_popover(app); + })) + .plugin(tauri_plugin_positioner::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_opener::init()) + .invoke_handler(tauri::generate_handler![ + bridge, + open_dashboard, + hide_popover, + quit_app, + pty::pty_spawn, + pty::pty_write, + pty::pty_resize, + pty::pty_kill + ]) + .setup(|app| { + pty::init(app); + + // macOS: menu-bar app, no dock icon. + #[cfg(target_os = "macos")] + app.set_activation_policy(tauri::ActivationPolicy::Accessory); + + // No menu — a click opens the popover directly (Quit / Open Dashboard + // live inside the popover). Anchor the popover under the tray icon. + let tray_icon = tauri::image::Image::from_bytes(include_bytes!("../icons/tray.png")) + .expect("valid tray icon"); + TrayIconBuilder::with_id("neo-tray") + .icon(tray_icon) + .icon_as_template(true) + .tooltip("Neo Desktop") + .on_tray_icon_event(|tray, event| { + tauri_plugin_positioner::on_tray_event(tray.app_handle(), &event); + if let TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } = event + { + toggle_popover(tray.app_handle()); + } + }) + .build(app)?; + + Ok(()) + }) + .on_window_event(|window, event| match event { + // Closing a window hides it — the tray process keeps running. + WindowEvent::CloseRequested { api, .. } => { + let _ = window.hide(); + api.prevent_close(); + } + // Dismiss the popover when it loses focus (click outside), like a menu. + WindowEvent::Focused(false) if window.label() == "popover" => { + let _ = window.hide(); + } + _ => {} + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs new file mode 100644 index 0000000..bea3c23 --- /dev/null +++ b/apps/desktop/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + desktop_lib::run() +} diff --git a/apps/desktop/src-tauri/src/pty.rs b/apps/desktop/src-tauri/src/pty.rs new file mode 100644 index 0000000..be6e59f --- /dev/null +++ b/apps/desktop/src-tauri/src/pty.rs @@ -0,0 +1,173 @@ +// Integrated terminals. Each session runs the bundled `neo-bridge pty …`, +// which opens a real remote PTY over neo's own SSH auth. +// +// We spawn it with std::process::Command (NOT portable-pty): with no pre_exec +// hook, std uses posix_spawn on macOS, so there's no fork inside this +// multithreaded WebKit process (which modern macOS aborts). We also read the +// child's stdout as RAW BYTES — tauri-plugin-shell would line-buffer it, which +// breaks a terminal (no per-keystroke echo, prompts without a trailing newline +// like `$ ` or `password:` never appear). +use std::collections::HashMap; +use std::io::{Read, Write}; +use std::process::{Child, ChildStdin, Command, Stdio}; +use std::sync::Mutex; + +use tauri::{Emitter, Manager}; + +struct Session { + child: Child, + stdin: ChildStdin, +} + +#[derive(Default)] +pub struct PtyState { + sessions: Mutex>, +} + +// Locate the bundled neo-bridge binary next to our executable. +fn bridge_path() -> Option { + let dir = std::env::current_exe().ok()?.parent()?.to_path_buf(); + for name in [ + "neo-bridge", + "neo-bridge-aarch64-apple-darwin", + "neo-bridge-x86_64-apple-darwin", + ] { + let cand = dir.join(name); + if cand.exists() { + return Some(cand); + } + } + None +} + +#[tauri::command] +pub fn pty_spawn( + app: tauri::AppHandle, + id: String, + server: String, + container: Option, + cols: u16, + rows: u16, +) -> Result<(), String> { + // Replace any existing session with this id. + if let Some(mut old) = app.state::().sessions.lock().unwrap().remove(&id) { + let _ = old.child.kill(); + } + + let bridge = bridge_path().ok_or_else(|| "neo-bridge not found".to_string())?; + let container_arg = container + .filter(|c| !c.is_empty()) + .unwrap_or_else(|| "-".to_string()); + + let mut child = Command::new(&bridge) + .arg("pty") + .arg(&server) + .arg(&container_arg) + .arg(cols.max(1).to_string()) + .arg(rows.max(1).to_string()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| e.to_string())?; + + let stdout = child.stdout.take().ok_or("no stdout")?; + let stderr = child.stderr.take().ok_or("no stderr")?; + let stdin = child.stdin.take().ok_or("no stdin")?; + + let data_ev = format!("pty://data/{id}"); + let exit_ev = format!("pty://exit/{id}"); + + // stdout → frontend (raw) + let app_out = app.clone(); + let ev_out = data_ev.clone(); + std::thread::spawn(move || pump(stdout, &app_out, &ev_out)); + + // stderr → frontend (raw); errors from the bridge surface in the terminal + let app_err = app.clone(); + let ev_err = data_ev.clone(); + std::thread::spawn(move || pump(stderr, &app_err, &ev_err)); + + app.state::() + .sessions + .lock() + .unwrap() + .insert(id.clone(), Session { child, stdin }); + + // Emit exit when stdout closes: spawn a waiter that reaps the child. + let app_reap = app.clone(); + std::thread::spawn(move || { + loop { + std::thread::sleep(std::time::Duration::from_millis(300)); + let state = app_reap.state::(); + let mut guard = state.sessions.lock().unwrap(); + if let Some(sess) = guard.get_mut(&id) { + match sess.child.try_wait() { + Ok(Some(_)) => { + guard.remove(&id); + drop(guard); + let _ = app_reap.emit(&exit_ev, ()); + return; + } + Ok(None) => {} + Err(_) => { + guard.remove(&id); + return; + } + } + } else { + return; // killed elsewhere + } + } + }); + + Ok(()) +} + +fn pump(mut r: R, app: &tauri::AppHandle, event: &str) { + let mut buf = [0u8; 8192]; + loop { + match r.read(&mut buf) { + Ok(0) | Err(_) => break, + Ok(n) => { + let _ = app.emit(event, String::from_utf8_lossy(&buf[..n]).to_string()); + } + } + } +} + +#[tauri::command] +pub fn pty_write(app: tauri::AppHandle, id: String, data: String) -> Result<(), String> { + let guard = app.state::(); + let mut sessions = guard.sessions.lock().unwrap(); + if let Some(sess) = sessions.get_mut(&id) { + sess.stdin + .write_all(data.as_bytes()) + .map_err(|e| e.to_string())?; + sess.stdin.flush().map_err(|e| e.to_string())?; + } + Ok(()) +} + +// Resize the remote PTY via a control frame the bridge intercepts on stdin. +#[tauri::command] +pub fn pty_resize(app: tauri::AppHandle, id: String, cols: u16, rows: u16) { + let guard = app.state::(); + let mut sessions = guard.sessions.lock().unwrap(); + if let Some(sess) = sessions.get_mut(&id) { + let frame = format!("\x1eR{}x{}\n", cols.max(1), rows.max(1)); + let _ = sess.stdin.write_all(frame.as_bytes()); + let _ = sess.stdin.flush(); + } +} + +#[tauri::command] +pub fn pty_kill(app: tauri::AppHandle, id: String) { + if let Some(mut sess) = app.state::().sessions.lock().unwrap().remove(&id) { + let _ = sess.child.kill(); + } +} + +pub fn init(app: &tauri::App) { + app.manage(PtyState::default()); +} diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json new file mode 100644 index 0000000..e3aad12 --- /dev/null +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Neo Desktop", + "version": "0.2.0", + "identifier": "dev.vxero.neo.desktop", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "label": "popover", + "title": "Neo", + "width": 300, + "height": 430, + "resizable": false, + "decorations": false, + "transparent": true, + "shadow": true, + "alwaysOnTop": true, + "skipTaskbar": true, + "visible": false, + "windowEffects": { + "effects": ["popover"], + "radius": 12, + "state": "active" + } + }, + { + "label": "main", + "title": "Neo Desktop", + "width": 960, + "height": 680, + "minWidth": 720, + "minHeight": 520, + "visible": false, + "transparent": true, + "titleBarStyle": "Overlay", + "hiddenTitle": true, + "windowEffects": { + "effects": ["sidebar"], + "state": "active" + } + } + ], + "security": { + "csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src ipc: http://ipc.localhost" + } + }, + "bundle": { + "active": true, + "targets": "all", + "externalBin": ["binaries/neo-bridge"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/apps/desktop/src/App.css b/apps/desktop/src/App.css new file mode 100644 index 0000000..85f7a4a --- /dev/null +++ b/apps/desktop/src/App.css @@ -0,0 +1,116 @@ +.logo.vite:hover { + filter: drop-shadow(0 0 2em #747bff); +} + +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafb); +} +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color: #0f0f0f; + background-color: #f6f6f6; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +.container { + margin: 0; + padding-top: 10vh; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: 0.75s; +} + +.logo.tauri:hover { + filter: drop-shadow(0 0 2em #24c8db); +} + +.row { + display: flex; + justify-content: center; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +h1 { + text-align: center; +} + +input, +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + color: #0f0f0f; + background-color: #ffffff; + transition: border-color 0.25s; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); +} + +button { + cursor: pointer; +} + +button:hover { + border-color: #396cd8; +} +button:active { + border-color: #396cd8; + background-color: #e8e8e8; +} + +input, +button { + outline: none; +} + +#greet-input { + margin-right: 5px; +} + +@media (prefers-color-scheme: dark) { + :root { + color: #f6f6f6; + background-color: #2f2f2f; + } + + a:hover { + color: #24c8db; + } + + input, + button { + color: #ffffff; + background-color: #0f0f0f98; + } + button:active { + background-color: #0f0f0f69; + } +} diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx new file mode 100644 index 0000000..1e7fc00 --- /dev/null +++ b/apps/desktop/src/App.tsx @@ -0,0 +1,25 @@ +import { useEffect, useState } from "react"; +import { Popover } from "./features/Popover"; +import { Dashboard } from "./features/Dashboard"; +import { ErrorBoundary } from "./components/ErrorBoundary"; + +// Render the popover or the full dashboard depending on which Tauri window we're in. +function useWindowLabel(): string { + const [label, setLabel] = useState("popover"); + useEffect(() => { + (async () => { + try { + const { getCurrentWindow } = await import("@tauri-apps/api/window"); + setLabel(getCurrentWindow().label); + } catch { + setLabel("popover"); + } + })(); + }, []); + return label; +} + +export default function App() { + const label = useWindowLabel(); + return {label === "main" ? : }; +} diff --git a/apps/desktop/src/assets/react.svg b/apps/desktop/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/apps/desktop/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop/src/components/ErrorBoundary.tsx b/apps/desktop/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..68a5805 --- /dev/null +++ b/apps/desktop/src/components/ErrorBoundary.tsx @@ -0,0 +1,34 @@ +import { Component, type ErrorInfo, type ReactNode } from "react"; + +interface State { + message: string | null; +} + +// Keeps a render crash from blanking the whole popover — shows the error instead. +export class ErrorBoundary extends Component<{ children: ReactNode }, State> { + state: State = { message: null }; + + static getDerivedStateFromError(error: unknown): State { + return { message: error instanceof Error ? error.message : String(error) }; + } + + componentDidCatch(error: unknown, info: ErrorInfo) { + console.error("UI error:", error, info); + } + + render() { + if (this.state.message) { + return ( +
+
+ UI error: {this.state.message} + +
+
+ ); + } + return this.props.children; + } +} diff --git a/apps/desktop/src/components/Modal.tsx b/apps/desktop/src/components/Modal.tsx new file mode 100644 index 0000000..28b0711 --- /dev/null +++ b/apps/desktop/src/components/Modal.tsx @@ -0,0 +1,29 @@ +import type { ReactNode } from "react"; + +// Lightweight centered modal. Click the backdrop or ✕ to dismiss. +export function Modal({ + title, + onClose, + children, + footer, + wide, +}: { + title: string; + onClose: () => void; + children: ReactNode; + footer?: ReactNode; + wide?: boolean; +}) { + return ( +
+
e.stopPropagation()}> +
+ {title} + +
+
{children}
+ {footer &&
{footer}
} +
+
+ ); +} diff --git a/apps/desktop/src/components/icons.tsx b/apps/desktop/src/components/icons.tsx new file mode 100644 index 0000000..beb30c0 --- /dev/null +++ b/apps/desktop/src/components/icons.tsx @@ -0,0 +1,75 @@ +// Minimal monochrome stroke icons (SF-Symbols-ish) for action buttons. +import type { SVGProps } from "react"; + +const base = { + width: 15, + height: 15, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: 1.8, + strokeLinecap: "round", + strokeLinejoin: "round", +} as const; + +type P = SVGProps; + +export const RestartIcon = (p: P) => ( + + + + +); + +export const PlayIcon = (p: P) => ( + + + +); + +export const StopIcon = (p: P) => ( + + + +); + +export const LogsIcon = (p: P) => ( + + + +); + +export const GlobeIcon = (p: P) => ( + + + + +); + +export const GearIcon = (p: P) => ( + + + + +); + +export const TrashIcon = (p: P) => ( + + + +); + +export const MoreIcon = (p: P) => ( + + + + + +); + +export const TerminalIcon = (p: P) => ( + + + + +); diff --git a/apps/desktop/src/features/Dashboard.tsx b/apps/desktop/src/features/Dashboard.tsx new file mode 100644 index 0000000..2d04047 --- /dev/null +++ b/apps/desktop/src/features/Dashboard.tsx @@ -0,0 +1,576 @@ +import { useEffect, useReducer, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { api } from "../lib/api"; +import { aggregateTrayState } from "../lib/desktop-api"; +import { terminals } from "../lib/terminals"; +import { toast, type Toast } from "../lib/toast"; +import { TerminalPanel } from "./Terminal"; +import { Modal } from "../components/Modal"; +import { + GlobeIcon, + LogsIcon, + MoreIcon, + PlayIcon, + RestartIcon, + StopIcon, + TerminalIcon, + TrashIcon, +} from "../components/icons"; +import type { AppSummary, Finding, ServerSnapshot, ServerSummary } from "../lib/protocol"; + +const pct = (u: number, t: number) => (t > 0 ? Math.round((u / t) * 100) : 0); +const gib = (b: number) => (b ? (b / 1024 ** 3).toFixed(1) : "0"); + +async function startDrag(e: React.MouseEvent) { + if (e.button !== 0) return; + if ((e.target as HTMLElement).closest("button,input,a,.term,.term-tab")) return; + try { + const { getCurrentWindow } = await import("@tauri-apps/api/window"); + await getCurrentWindow().startDragging(); + } catch { + /* browser preview */ + } +} + +export function Dashboard() { + const [servers, setServers] = useState([]); + const [selected, setSelected] = useState(""); + const [err, setErr] = useState(""); + const [termOpen, setTermOpen] = useState(false); + const [termFull, setTermFull] = useState(false); + + useEffect(() => { + api + .listServers() + .then((s) => { + setServers(s); + setSelected(s.find((x) => x.current)?.name ?? s[0]?.name ?? ""); + }) + .catch((e) => setErr(String(e))); + terminals.onReveal(() => setTermOpen(true)); + }, []); + + const current = servers.find((s) => s.name === selected); + + function toggleTerm() { + setTermOpen((o) => { + const next = !o; + if (next && terminals.tabs.length === 0 && current) { + terminals.openServer({ server: current.name, host: current.host, port: current.port, keyPath: current.keyPath }); + } + return next; + }); + } + + return ( +
+ + +
+ {current ? ( + + ) : ( +
+ Select a server +
+ )} + + setTermFull((v) => !v)} + onClose={() => { setTermOpen(false); setTermFull(false); }} + /> +
+ + +
+ ); +} + +function TermDrawer({ + current, + open, + full, + onToggleFull, + onClose, +}: { + current: ServerSummary | undefined; + open: boolean; + full: boolean; + onToggleFull: () => void; + onClose: () => void; +}) { + const [, force] = useReducer((x) => x + 1, 0); + useEffect(() => terminals.subscribe(force), []); + const tabs = terminals.tabs; + const activeId = terminals.activeId; + + function addServer() { + if (current && !terminals.atLimit()) { + terminals.openServer({ server: current.name, host: current.host, port: current.port, keyPath: current.keyPath }); + } + } + + return ( +
+
open && onToggleFull()}> +
+ {tabs.map((t) => ( + + ))} + +
+ + + + +
+
+ {tabs.length === 0 ? ( +
No terminal open. Click + for a server shell, or an app's “Container shell”.
+ ) : ( + tabs.map((t) => ) + )} +
+
+ ); +} + +function Toaster() { + const [items, setItems] = useState([]); + useEffect(() => toast.subscribe(setItems), []); + return ( +
+ {items.map((t) => ( +
+ {t.msg} +
+ ))} +
+ ); +} + +function SideRow({ server, active, onClick }: { server: ServerSummary; active: boolean; onClick: () => void }) { + return ( + + ); +} + +function Detail({ + server, + termOpen, + onToggleTerm, +}: { + server: ServerSummary; + termOpen: boolean; + onToggleTerm: () => void; +}) { + const [snap, setSnap] = useState(null); + const [apps, setApps] = useState([]); + const [findings, setFindings] = useState([]); + const [loading, setLoading] = useState(true); + + async function load() { + setLoading(true); + try { + const s = await api.getSnapshot(server.name); + setSnap(s); + if (s.reachable) { + const [a, f] = await Promise.all([api.listApps(server.name), api.runDiagnostics(server.name)]); + setApps(a ?? []); + setFindings(f ?? []); + } else { + setApps([]); + setFindings([]); + } + } catch { + setSnap((prev) => prev ?? ({ server: server.name, reachable: false } as ServerSnapshot)); + } finally { + setLoading(false); + } + } + + useEffect(() => { + load(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [server.name]); + + const state = snap ? aggregateTrayState([snap], findings) : "unknown"; + + return ( + <> +
+
+ +

{server.name}

+ {server.host} +
+ + +
+ +
+ {loading && !snap ? ( +
Connecting over SSH…
+ ) : snap && snap.reachable ? ( + <> +
+ + + + +
+ + {findings.length > 0 && ( +
+
Alerts
+
+ {findings.map((f) => ( +
+ + {f.summary} +
+ ))} +
+
+ )} + +
+
+ Applications + {snap.apps.running}/{snap.apps.total} running +
+
+ {apps.length === 0 ? ( +
No apps installed
+ ) : ( + apps.map((a) => ) + )} +
+
+ + ) : ( +
Server unreachable
+ )} +
+ + ); +} + +type Confirm = { action: "restart" | "stop" | "remove"; danger?: boolean } | null; + +function AppRow({ app, srv, onChanged }: { app: AppSummary; srv: ServerSummary; onChanged: () => void }) { + const [status, setStatus] = useState(app.status); + const [busy, setBusy] = useState(false); + const [menu, setMenu] = useState(false); + const [menuPos, setMenuPos] = useState<{ top: number; left: number } | null>(null); + const [confirm, setConfirm] = useState(null); + const [logsOpen, setLogsOpen] = useState(false); + const [domainOpen, setDomainOpen] = useState(false); + const menuRef = useRef(null); + const moreRef = useRef(null); + const running = status === "running"; + + function openMenu() { + const r = moreRef.current?.getBoundingClientRect(); + if (r) setMenuPos({ top: r.bottom + 4, left: Math.max(8, r.right - 186) }); + setMenu(true); + } + + useEffect(() => { + if (!menu) return; + const away = (e: MouseEvent) => { + const t = e.target as Node; + if (menuRef.current?.contains(t) || moreRef.current?.contains(t)) return; + setMenu(false); + }; + window.addEventListener("mousedown", away); + return () => window.removeEventListener("mousedown", away); + }, [menu]); + + async function act(action: "restart" | "stop" | "start" | "remove") { + setBusy(true); + setConfirm(null); + try { + const r = (await api.runAppAction({ server: srv.name, app: app.name, action: action as never })) as unknown as { status?: string }; + const past = action === "stop" ? "stopped" : action === "start" ? "started" : action === "remove" ? "removed" : "restarted"; + if (action !== "remove") setStatus((r.status as typeof status) ?? (action === "stop" ? "stopped" : "running")); + toast.show(`${app.name} ${past}`, "ok"); + onChanged(); + } catch (e) { + toast.show(`${app.name}: ${String(e).replace(/^Error:\s*/, "")}`, "err"); + } finally { + setBusy(false); + } + } + + function containerShell() { + setMenu(false); + terminals.openContainer({ server: srv.name, host: srv.host, port: srv.port, keyPath: srv.keyPath, app: app.name }); + } + + return ( +
+ + {app.name} + {app.domain || app.image} + {status} + + {busy ? ( + + ) : running ? ( + <> + + + + ) : ( + + )} + + {menu && menuPos && + createPortal( +
+ + + + +
, + document.body + )} +
+ + {confirm && ( + setConfirm(null)} + onConfirm={() => act(confirm.action)} + /> + )} + {logsOpen && setLogsOpen(false)} />} + {domainOpen && ( + setDomainOpen(false)} + onSaved={(d) => { toast.show(`${app.name} → ${d}`, "ok"); setDomainOpen(false); onChanged(); }} + /> + )} +
+ ); +} + +function ConfirmDialog({ + appName, + action, + danger, + onCancel, + onConfirm, +}: { + appName: string; + action: string; + danger?: boolean; + onCancel: () => void; + onConfirm: () => void; +}) { + const verb = action[0].toUpperCase() + action.slice(1); + return ( + + + + + } + > + {action === "remove" ? ( +

+ This stops and removes the {appName} container and its proxy route. Volumes (data) are kept. +

+ ) : ( +

+ {verb} the {appName} container now? +

+ )} +
+ ); +} + +function LogsModal({ server, app, onClose }: { server: string; app: string; onClose: () => void }) { + const [logs, setLogs] = useState(null); + const [err, setErr] = useState(""); + const preRef = useRef(null); + + async function load() { + setLogs(null); + setErr(""); + try { + const l = await api.getLogs(server, app); + setLogs(l || "(no output)"); + } catch (e) { + setErr(String(e).replace(/^Error:\s*/, "")); + } + } + useEffect(() => { + load(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + if (preRef.current) preRef.current.scrollTop = preRef.current.scrollHeight; + }, [logs]); + + return ( + + last 500 lines + + + + } + > + {err ? ( +
{err}
+ ) : logs === null ? ( +
loading…
+ ) : ( +
{logs}
+ )} +
+ ); +} + +function DomainDialog({ + server, + app, + current, + onClose, + onSaved, +}: { + server: string; + app: string; + current: string; + onClose: () => void; + onSaved: (domain: string) => void; +}) { + const [domain, setDomain] = useState(current || ""); + const [https, setHttps] = useState(true); + const [busy, setBusy] = useState(false); + const [err, setErr] = useState(""); + + async function save() { + const d = domain.trim(); + if (!d) return; + setBusy(true); + setErr(""); + try { + await api.setDomain({ server, app, domain: d, https }); + onSaved(d); + } catch (e) { + setErr(String(e).replace(/^Error:\s*/, "")); + setBusy(false); + } + } + + return ( + + + + + } + > + + +

Point this domain's DNS A record at the server first.

+ {err &&
{err}
} +
+ ); +} + +function Tile({ label, value, bar, sub }: { label: string; value: string; bar?: number; sub?: string }) { + const level = bar == null ? "ok" : bar >= 90 ? "critical" : bar >= 75 ? "warning" : "ok"; + return ( +
+
{label}
+
{value}
+ {bar != null && ( +
+
+
+ )} + {sub &&
{sub}
} +
+ ); +} diff --git a/apps/desktop/src/features/Popover.tsx b/apps/desktop/src/features/Popover.tsx new file mode 100644 index 0000000..1e33b80 --- /dev/null +++ b/apps/desktop/src/features/Popover.tsx @@ -0,0 +1,295 @@ +import { useEffect, useState, type ReactNode } from "react"; +import { api } from "../lib/api"; +import { aggregateTrayState } from "../lib/desktop-api"; +import type { + AppSummary, + BridgeHello, + Finding, + ServerSnapshot, + ServerSummary, + TrayState, +} from "../lib/protocol"; + +type View = "home" | "servers" | "apps"; + +async function invokeCmd(cmd: string) { + try { + const { invoke } = await import("@tauri-apps/api/core"); + await invoke(cmd); + } catch { + /* browser preview */ + } +} + +const pct = (u: number, t: number) => (t > 0 ? Math.round((u / t) * 100) : 0); + +export function Popover() { + const [hello, setHello] = useState(null); + const [servers, setServers] = useState([]); + const [selected, setSelected] = useState(""); + const [err, setErr] = useState(""); + const [view, setView] = useState("home"); + + useEffect(() => { + (async () => { + try { + const [h, s] = await Promise.all([api.hello(), api.listServers()]); + setHello(h); + setServers(s); + setSelected(s.find((x) => x.current)?.name ?? s[0]?.name ?? ""); + } catch (e) { + setErr(String(e)); + } + })(); + }, []); + + const current = servers.find((s) => s.name === selected); + + return ( +
+
+
+ + Neo +
+
+ {hello && !hello.activated ? ( + activate + ) : ( + hello && {hello.cliCore} + )} +
+
+ + {err &&
{err}
} + + {view === "home" && current && ( + setView("servers")} onAllApps={() => setView("apps")} /> + )} + {view === "home" && !current && !err &&
No servers.
} + + {view === "servers" && ( + { + setSelected(name); + setView("home"); + }} + onBack={() => setView("home")} + /> + )} + {view === "apps" && setView("home")} />} + +
+ + +
+
+ ); +} + +function StatusHome({ + server, + onSwitch, + onAllApps, +}: { + server: ServerSummary; + onSwitch: () => void; + onAllApps: () => void; +}) { + const [snap, setSnap] = useState(null); + const [apps, setApps] = useState([]); + const [findings, setFindings] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + + async function load() { + setLoading(true); + setError(""); + try { + const s = await api.getSnapshot(server.name); + setSnap(s); + if (s.reachable) { + const [a, f] = await Promise.all([api.listApps(server.name), api.runDiagnostics(server.name)]); + setApps(a ?? []); + setFindings(f ?? []); + } else { + setApps([]); + setFindings([]); + } + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + } + } + useEffect(() => { + load(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [server.name]); + + const state: TrayState = snap ? aggregateTrayState([snap], findings) : "unknown"; + const status = loading + ? "connecting…" + : error || (snap && !snap.reachable) + ? "offline" + : snap + ? `${snap.latencyMs}ms` + : ""; + + return ( +
+ + + {snap && snap.reachable ? ( + <> +
+ + + +
+ UP + {Math.floor(snap.uptimeSeconds / 86400)}d +
+
+ + {findings.slice(0, 1).map((f) => ( +
+ {f.summary} +
+ ))} + + +
+ {apps.length === 0 ? ( +
No apps
+ ) : ( + apps.slice(0, 3).map((a) => ( +
+ + {a.name} + {a.domain || a.status} +
+ )) + )} + {apps.length > 3 && ( + + )} +
+ + ) : loading ? ( +
connecting over SSH…
+ ) : ( +
+ {error || "unreachable"} + +
+ )} +
+ ); +} + +// Compact horizontal gauge: label + inline bar + %. +function Gauge({ label, v }: { label: string; v: number }) { + const level = v >= 90 ? "critical" : v >= 75 ? "warning" : "ok"; + return ( +
+ {label} +
+
+
+ {v}% +
+ ); +} + +function BackBar({ title, onBack, right }: { title: string; onBack: () => void; right?: ReactNode }) { + return ( +
+ + {title} + {right} +
+ ); +} + +function ServersView({ + servers, + selected, + onPick, + onBack, +}: { + servers: ServerSummary[]; + selected: string; + onPick: (n: string) => void; + onBack: () => void; +}) { + return ( +
+ +
+ {servers.map((s) => ( + + ))} +
+
+ ); +} + +function AppsView({ server, onBack }: { server: string; onBack: () => void }) { + const [apps, setApps] = useState(null); + const [error, setError] = useState(""); + + useEffect(() => { + (async () => { + try { + setApps(await api.listApps(server)); + } catch (e) { + setError(String(e)); + } + })(); + }, [server]); + + return ( +
+ + {error ? ( +
{error}
+ ) : apps === null ? ( +
loading…
+ ) : apps.length === 0 ? ( +
No apps installed.
+ ) : ( +
+ {apps.map((a) => ( +
+ + {a.name} + {a.domain || a.status} +
+ ))} +
+ )} +
+ ); +} diff --git a/apps/desktop/src/features/Terminal.tsx b/apps/desktop/src/features/Terminal.tsx new file mode 100644 index 0000000..0550a56 --- /dev/null +++ b/apps/desktop/src/features/Terminal.tsx @@ -0,0 +1,113 @@ +import { useEffect, useRef } from "react"; +import { Terminal as XTerm } from "@xterm/xterm"; +import { FitAddon } from "@xterm/addon-fit"; +import "@xterm/xterm/css/xterm.css"; +import type { TermTab } from "../lib/terminals"; + +// One session per tab. Rust `pty_spawn` runs the bundled `neo-bridge pty …` +// sidecar (remote PTY over neo's SSH auth). We keep xterm and the remote PTY +// the same size by fitting and pushing the dimensions on every layout change. +export function TerminalPanel({ tab, active }: { tab: TermTab; active: boolean }) { + const ref = useRef(null); + const syncRef = useRef<() => void>(() => {}); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const term = new XTerm({ + fontFamily: '"SF Mono", Menlo, Monaco, "Cascadia Code", monospace', + fontSize: 12, + cursorBlink: true, + allowProposedApi: true, + theme: { + background: "#00000000", + foreground: "#e6e6e6", + cursor: "#0a84ff", + selectionBackground: "rgba(10,132,255,0.35)", + }, + }); + const fit = new FitAddon(); + term.loadAddon(fit); + term.open(el); + + let cleanup: Array<() => void> = []; + let disposed = false; + + (async () => { + const core = await import("@tauri-apps/api/core"); + const ev = await import("@tauri-apps/api/event"); + + // Fit xterm to the element and push the exact size to the remote PTY. + const sync = () => { + try { + fit.fit(); + } catch { + return; + } + if (term.cols > 0 && term.rows > 0) { + core.invoke("pty_resize", { id: tab.id, cols: term.cols, rows: term.rows }); + } + }; + syncRef.current = sync; + + // Wait two frames so the (now un-animated) drawer has its final height, + // then fit before spawning so the shell draws at the right width. + await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r()))); + if (disposed) return; + try { + fit.fit(); + } catch { + /* ignore */ + } + + const { spec } = tab; + term.writeln( + `\x1b[90mConnecting to ${spec.server}${spec.kind === "container" ? ` → ${spec.app}` : ""}…\x1b[0m` + ); + + const unlisten = await ev.listen(`pty://data/${tab.id}`, (e) => term.write(e.payload)); + const unexit = await ev.listen(`pty://exit/${tab.id}`, () => term.writeln("\r\n\x1b[90m[disconnected]\x1b[0m")); + const onData = term.onData((d) => core.invoke("pty_write", { id: tab.id, data: d })); + + try { + await core.invoke("pty_spawn", { + id: tab.id, + server: spec.server, + container: spec.kind === "container" ? spec.app : null, + cols: term.cols || 80, + rows: term.rows || 24, + }); + } catch (e) { + term.writeln(`\r\n\x1b[31mfailed to start: ${String(e)}\x1b[0m`); + } + + const ro = new ResizeObserver(() => sync()); + ro.observe(el); + // One more sync after the shell has drawn its first prompt. + setTimeout(sync, 120); + + cleanup = [ + () => unlisten(), + () => unexit(), + () => onData.dispose(), + () => ro.disconnect(), + () => core.invoke("pty_kill", { id: tab.id }), + ]; + })(); + + return () => { + disposed = true; + cleanup.forEach((fn) => fn()); + term.dispose(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tab.id]); + + // Re-sync when this tab becomes active (it was display:none, so unmeasurable). + useEffect(() => { + if (active) requestAnimationFrame(() => syncRef.current()); + }, [active]); + + return
; +} diff --git a/apps/desktop/src/lib/api.ts b/apps/desktop/src/lib/api.ts new file mode 100644 index 0000000..cc01b0a --- /dev/null +++ b/apps/desktop/src/lib/api.ts @@ -0,0 +1,10 @@ +import type { DesktopAPI } from "./protocol"; +import { fixtureApi } from "./desktop-api"; +import { tauriApi } from "./tauri-api"; + +// Use the real bridge inside Tauri; fall back to fixtures in a plain browser +// (Storybook-style development and tests). +const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; + +export const api: DesktopAPI = isTauri ? tauriApi : fixtureApi; +export const usingFixtures = !isTauri; diff --git a/apps/desktop/src/lib/desktop-api.ts b/apps/desktop/src/lib/desktop-api.ts new file mode 100644 index 0000000..fab0018 --- /dev/null +++ b/apps/desktop/src/lib/desktop-api.ts @@ -0,0 +1,120 @@ +// Fixture DesktopAPI — lets the tray UI render without a bridge or SSH server. +// Production builds will swap this for the Tauri stdio transport. + +import type { + AppActionInput, + AppSummary, + BridgeHello, + DesktopAPI, + Finding, + ServerSnapshot, + ServerSummary, + TrayState, +} from "./protocol"; + +const GiB = 1024 * 1024 * 1024; + +const SERVERS: ServerSummary[] = [ + { name: "production", host: "root@159.65.100.42", port: 22, keyPath: "", current: true }, + { name: "staging", host: "ubuntu@95.41.31.91", port: 22, keyPath: "", current: false }, + { name: "edge-eu", host: "root@203.0.113.10", port: 22, keyPath: "", current: false }, +]; + +const SNAPSHOTS: Record = { + production: { + server: "production", + reachable: true, + observedAt: "2026-07-20T05:40:00Z", + latencyMs: 84, + cpuPercent: 34, + ramUsedBytes: 2.1 * GiB, + ramTotalBytes: 4 * GiB, + diskUsedBytes: 31 * GiB, + diskTotalBytes: 80 * GiB, + uptimeSeconds: 1_820_400, + apps: { total: 3, running: 3 }, + services: { total: 2, running: 2 }, + }, + staging: { + server: "staging", + reachable: true, + observedAt: "2026-07-20T05:39:30Z", + latencyMs: 240, + cpuPercent: 82, + ramUsedBytes: 3.6 * GiB, + ramTotalBytes: 4 * GiB, + diskUsedBytes: 61 * GiB, + diskTotalBytes: 80 * GiB, + uptimeSeconds: 320_000, + apps: { total: 2, running: 1 }, + services: { total: 1, running: 1 }, + }, + "edge-eu": { + server: "edge-eu", + reachable: false, + observedAt: "2026-07-20T05:35:00Z", + latencyMs: 0, + cpuPercent: 0, + ramUsedBytes: 0, + ramTotalBytes: 0, + diskUsedBytes: 0, + diskTotalBytes: 0, + uptimeSeconds: 0, + apps: { total: 4, running: 0 }, + services: { total: 2, running: 0 }, + }, +}; + +const APPS: Record = { + production: [ + { name: "ghost", domain: "blog.mysite.com", status: "running", image: "ghost:5-alpine" }, + { name: "plausible", domain: "analytics.mysite.com", status: "running", image: "plausible/community:v2" }, + { name: "gitea", domain: "git.mysite.com", status: "running", image: "gitea/gitea:1.22" }, + ], + staging: [ + { name: "api", domain: "api.staging.mysite.com", status: "running", image: "node:20" }, + { name: "worker", domain: "", status: "stopped", image: "node:20" }, + ], + "edge-eu": [], +}; + +const FINDINGS: Record = { + production: [], + staging: [ + { id: "f1", rule: "cpu", severity: "warning", summary: "CPU at 82% for 3 samples", lastObservedAt: "2026-07-20T05:39:30Z" }, + { id: "f2", rule: "app_state", severity: "warning", summary: "App 'worker' is stopped", lastObservedAt: "2026-07-20T05:39:30Z" }, + ], + "edge-eu": [ + { id: "f3", rule: "reachability", severity: "critical", summary: "edge-eu is unreachable (2 attempts)", lastObservedAt: "2026-07-20T05:35:00Z" }, + ], +}; + +const delay = (v: T, ms = 220) => new Promise((r) => setTimeout(() => r(v), ms)); + +export const fixtureApi: DesktopAPI = { + hello: () => + delay({ + protocolVersion: 1, + bridgeVersion: "0.1.0-fixture", + cliCore: "0.22.0", + platform: "darwin/arm64", + activated: true, + }), + listServers: () => delay(SERVERS), + getSnapshot: (server) => delay(SNAPSHOTS[server] ?? SNAPSHOTS.production), + listApps: (server) => delay(APPS[server] ?? []), + runAppAction: (input: AppActionInput) => + delay({ operationId: "op-1", status: "succeeded", summary: `${input.action} ${input.app} ok` }, 500), + runDiagnostics: (server) => delay(FINDINGS[server] ?? []), + getLogs: (_server, app) => delay(`[fixture] last logs for ${app}\n2026-07-22 12:00:00 started\n2026-07-22 12:00:01 ready`, 300), + setDomain: (input) => delay({ status: input.domain }, 400), + getSshKey: () => delay("", 100), +}; + +// Aggregate several servers into a single tray state. +export function aggregateTrayState(snapshots: ServerSnapshot[], findings: Finding[]): TrayState { + if (snapshots.length === 0) return "unknown"; + if (snapshots.some((s) => !s.reachable) || findings.some((f) => f.severity === "critical")) return "critical"; + if (findings.some((f) => f.severity === "warning")) return "warning"; + return "healthy"; +} diff --git a/apps/desktop/src/lib/protocol.ts b/apps/desktop/src/lib/protocol.ts new file mode 100644 index 0000000..ec4d0cd --- /dev/null +++ b/apps/desktop/src/lib/protocol.ts @@ -0,0 +1,87 @@ +// Shared types between the desktop UI and the (future) neo-bridge sidecar. +// Phase 1 uses a fixture implementation; the transport is swapped in later. + +export type Severity = "info" | "warning" | "critical"; +export type TrayState = "healthy" | "warning" | "critical" | "unknown"; + +export interface BridgeHello { + protocolVersion: number; + bridgeVersion: string; + cliCore: string; + platform: string; + activated: boolean; +} + +export interface ServerSummary { + name: string; + host: string; + port: number; + keyPath: string; + current: boolean; +} + +export interface WorkloadCounts { + total: number; + running: number; +} + +export interface ServerSnapshot { + server: string; + reachable: boolean; + observedAt: string; // ISO + latencyMs: number; + cpuPercent: number; + ramUsedBytes: number; + ramTotalBytes: number; + diskUsedBytes: number; + diskTotalBytes: number; + uptimeSeconds: number; + apps: WorkloadCounts; + services: WorkloadCounts; +} + +export interface AppSummary { + name: string; + domain: string; + status: "running" | "stopped" | "restarting" | "unhealthy"; + image: string; +} + +export interface Finding { + id: string; + rule: string; + severity: Severity; + summary: string; + lastObservedAt: string; +} + +export interface AppActionInput { + server: string; + app: string; + action: "start" | "stop" | "restart"; +} + +export interface OperationResult { + operationId: string; + status: "succeeded" | "failed"; + summary: string; +} + +export interface DomainInput { + server: string; + app: string; + domain: string; + https: boolean; +} + +export interface DesktopAPI { + hello(): Promise; + listServers(): Promise; + getSnapshot(server: string): Promise; + listApps(server: string): Promise; + runAppAction(input: AppActionInput): Promise; + runDiagnostics(server: string): Promise; + getLogs(server: string, app: string): Promise; + setDomain(input: DomainInput): Promise<{ status?: string }>; + getSshKey(server: string): Promise; +} diff --git a/apps/desktop/src/lib/tauri-api.ts b/apps/desktop/src/lib/tauri-api.ts new file mode 100644 index 0000000..7ce083a --- /dev/null +++ b/apps/desktop/src/lib/tauri-api.ts @@ -0,0 +1,40 @@ +// Real DesktopAPI: proxies to the neo-bridge sidecar through the typed Tauri +// `bridge` command. Each call runs one bridge invocation (read-only). + +import type { + AppSummary, + BridgeHello, + DesktopAPI, + Finding, + ServerSnapshot, + ServerSummary, +} from "./protocol"; + +interface Envelope { + version: number; + result?: T; + error?: { code: string; message: string }; +} + +async function call(method: string, params?: Record): Promise { + const { invoke } = await import("@tauri-apps/api/core"); + const raw = await invoke("bridge", { + method, + params: params ? JSON.stringify(params) : undefined, + }); + const env = JSON.parse(raw) as Envelope; + if (env.error) throw new Error(`${env.error.code}: ${env.error.message}`); + return env.result as T; +} + +export const tauriApi: DesktopAPI = { + hello: () => call("bridge.hello"), + listServers: () => call("server.list"), + getSnapshot: (server) => call("server.snapshot", { server }), + listApps: (server) => call("app.list", { server }), + runAppAction: (input) => call("app.action", input as unknown as Record), + runDiagnostics: (server) => call("diagnostics.run", { server }), + getLogs: async (server, app) => (await call<{ logs: string }>("app.logs", { server, app })).logs, + setDomain: (input) => call("app.domain", input as unknown as Record), + getSshKey: async (server) => (await call<{ keyPath: string }>("server.sshkey", { server })).keyPath, +}; diff --git a/apps/desktop/src/lib/terminalBus.ts b/apps/desktop/src/lib/terminalBus.ts new file mode 100644 index 0000000..fb1d8dd --- /dev/null +++ b/apps/desktop/src/lib/terminalBus.ts @@ -0,0 +1,25 @@ +// Bridges GUI action buttons and the integrated terminal: a button calls +// terminalBus.run("neo ..."), which is typed into the live PTY session. +type Handler = (cmd: string) => void; + +let handler: Handler | null = null; +let onShow: (() => void) | null = null; +const queue: string[] = []; + +export const terminalBus = { + register(h: Handler): () => void { + handler = h; + queue.splice(0).forEach(h); + return () => { + if (handler === h) handler = null; + }; + }, + onReveal(cb: () => void) { + onShow = cb; + }, + run(cmd: string) { + onShow?.(); + if (handler) handler(cmd); + else queue.push(cmd); + }, +}; diff --git a/apps/desktop/src/lib/terminals.ts b/apps/desktop/src/lib/terminals.ts new file mode 100644 index 0000000..d6337a0 --- /dev/null +++ b/apps/desktop/src/lib/terminals.ts @@ -0,0 +1,75 @@ +// Manages the open terminal tabs (server shells + container shells). +export interface TermSpec { + kind: "server" | "container"; + server: string; // server name (for key lookup) + host: string; // user@ip + port: number; + keyPath: string; + app?: string; // container name (kind === "container") +} + +export interface TermTab { + id: string; + title: string; + spec: TermSpec; +} + +const MAX = 6; +let tabs: TermTab[] = []; +let activeId = ""; +let counter = 0; +const subs = new Set<() => void>(); +let reveal: (() => void) | null = null; + +function emit() { + subs.forEach((f) => f()); +} + +export const terminals = { + MAX, + get tabs() { + return tabs; + }, + get activeId() { + return activeId; + }, + atLimit() { + return tabs.length >= MAX; + }, + subscribe(f: () => void): () => void { + subs.add(f); + return () => { + subs.delete(f); + }; + }, + onReveal(cb: () => void) { + reveal = cb; + }, + setActive(id: string) { + activeId = id; + emit(); + }, + openServer(spec: Omit): string | null { + if (tabs.length >= MAX) return null; + const id = `t${++counter}`; + tabs = [...tabs, { id, title: spec.server, spec: { ...spec, kind: "server" } }]; + activeId = id; + reveal?.(); + emit(); + return id; + }, + openContainer(spec: Omit): string | null { + if (tabs.length >= MAX) return null; + const id = `t${++counter}`; + tabs = [...tabs, { id, title: spec.app ?? "shell", spec: { ...spec, kind: "container" } }]; + activeId = id; + reveal?.(); + emit(); + return id; + }, + close(id: string) { + tabs = tabs.filter((t) => t.id !== id); + if (activeId === id) activeId = tabs[tabs.length - 1]?.id ?? ""; + emit(); + }, +}; diff --git a/apps/desktop/src/lib/toast.ts b/apps/desktop/src/lib/toast.ts new file mode 100644 index 0000000..ca809af --- /dev/null +++ b/apps/desktop/src/lib/toast.ts @@ -0,0 +1,34 @@ +// Tiny toast store for GUI action feedback (restart succeeded, etc.). +export type ToastKind = "ok" | "err" | "info"; +export interface Toast { + id: number; + kind: ToastKind; + msg: string; +} + +let items: Toast[] = []; +let seq = 1; +const subs = new Set<(t: Toast[]) => void>(); + +function emit() { + subs.forEach((f) => f(items)); +} + +export const toast = { + show(msg: string, kind: ToastKind = "info") { + const id = seq++; + items = [...items, { id, kind, msg }]; + emit(); + setTimeout(() => { + items = items.filter((t) => t.id !== id); + emit(); + }, 3200); + }, + subscribe(f: (t: Toast[]) => void): () => void { + subs.add(f); + f(items); + return () => { + subs.delete(f); + }; + }, +}; diff --git a/apps/desktop/src/main.tsx b/apps/desktop/src/main.tsx new file mode 100644 index 0000000..4082d20 --- /dev/null +++ b/apps/desktop/src/main.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./styles/app.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/apps/desktop/src/styles/app.css b/apps/desktop/src/styles/app.css new file mode 100644 index 0000000..d20434d --- /dev/null +++ b/apps/desktop/src/styles/app.css @@ -0,0 +1,496 @@ +:root { + /* macOS dark system palette */ + --text: rgba(255, 255, 255, 0.92); + --muted: rgba(235, 235, 245, 0.55); + --faint: rgba(235, 235, 245, 0.35); + --hairline: rgba(255, 255, 255, 0.10); + --fill: rgba(120, 120, 128, 0.20); + --fill-hover: rgba(120, 120, 128, 0.32); + --sel: rgba(10, 132, 255, 0.28); + --blue: #0a84ff; + --green: #32d74b; + --amber: #ff9f0a; + --red: #ff453a; + --gray: #98989d; + + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif; + font-size: 13px; + color-scheme: dark; +} + +* { box-sizing: border-box; margin: 0; padding: 0; } +html, body, #root { height: 100%; } +body { + background: transparent; + color: var(--text); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + user-select: none; + cursor: default; +} +button { font: inherit; } + +.muted { color: var(--muted); } +.small { font-size: 12px; } +.xsmall { font-size: 11px; } +.ok { color: var(--green); } +.mt { margin-top: 6px; } + +/* ---------- Popover (menu-bar) ---------- */ +.popover { + height: 100vh; + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px; + /* native vibrancy shows through; this is a faint tint over it */ + background: rgba(30, 30, 34, 0.28); + border: 0.5px solid var(--hairline); + border-radius: 12px; + overflow: hidden; +} + +.pop-head { + display: flex; align-items: center; justify-content: space-between; + padding: 1px 4px 6px; + border-bottom: 0.5px solid var(--hairline); +} +.brand { display: flex; align-items: center; gap: 5px; font-weight: 600; font-size: 13px; letter-spacing: -0.01em; } +.logo { color: var(--amber); } +.ver { color: var(--faint); font-size: 11px; font-weight: 500; } +.head-right { display: flex; align-items: center; } + +/* compact server pill */ +.server-pill { + display: flex; align-items: center; gap: 7px; + width: 100%; padding: 7px 9px; + background: var(--fill); border: none; border-radius: 8px; + color: var(--text); cursor: pointer; text-align: left; + transition: background 0.12s ease; +} +.server-pill:hover { background: var(--fill-hover); } +.pill-name { font-weight: 600; font-size: 13px; } +.pill-status { flex: 1; color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; } + +/* compact inline gauges */ +.gauges { display: flex; flex-direction: column; gap: 6px; padding: 2px 2px; } +.gauge { display: flex; align-items: center; gap: 8px; } +.gauge .muted { width: 26px; flex-shrink: 0; } +.gauge .bar { flex: 1; height: 5px; margin: 0; } +.gauge-val { width: 34px; text-align: right; font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; } +.up { display: flex; align-items: center; gap: 8px; } +.up .muted { width: 26px; flex-shrink: 0; } +.up-val { flex: 1; font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; } + +/* rows behave like macOS menu items: no border, hover fill */ +.cur-server, .menu-row, .list-row { + display: flex; align-items: center; gap: 8px; + width: 100%; text-align: left; + padding: 8px 10px; + background: transparent; + border: none; border-radius: 7px; + color: var(--text); cursor: pointer; + transition: background 0.12s ease; +} +.cur-server:hover, .menu-row:hover, .list-row:hover { background: var(--fill); } +.cur-server:active, .menu-row:active, .list-row:active { background: var(--fill-hover); } +.list-row.active { background: var(--sel); } + +.cur-server { background: var(--fill); } +.cur-name { font-weight: 600; } +.cur-server .muted { flex: 1; } +.chev { color: var(--faint); font-size: 12px; } + +.menu { display: flex; flex-direction: column; gap: 1px; } +.menu-label { font-weight: 500; flex: 1; } + +/* macOS grouped-list section label */ +.sec-label { + font-size: 11px; font-weight: 600; letter-spacing: 0.02em; + color: var(--faint); text-transform: uppercase; + padding: 2px 6px 0; +} +.sec-head { + display: flex; align-items: center; gap: 8px; + background: none; border: none; color: inherit; cursor: pointer; + padding: 4px 6px 0; text-align: left; +} +.sec-head .sec-label { padding: 0; } +.sec-head:hover .sec-label, .sec-head:hover .chev { color: var(--muted); } +.sec-count { margin-left: auto; font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; } +.hint { text-align: center; padding: 4px 0 0; color: var(--faint); } + +.view { display: flex; flex-direction: column; gap: 8px; flex: 1; min-height: 0; overflow: auto; } +.backbar { + display: flex; align-items: center; gap: 6px; + padding-bottom: 6px; border-bottom: 0.5px solid var(--hairline); +} +.back { background: none; border: none; color: var(--blue); cursor: pointer; font-size: 13px; padding: 2px 0; font-weight: 500; } +.back:hover { opacity: 0.7; } +.backtitle { flex: 1; font-weight: 600; text-align: center; } +.backright { margin-left: auto; min-width: 26px; } +.mini { + background: var(--fill); border: none; color: var(--text); + border-radius: 6px; width: 26px; height: 24px; cursor: pointer; +} +.mini:hover { background: var(--fill-hover); } + +.list { display: flex; flex-direction: column; gap: 1px; } +.list-row .muted { flex: 1; } + +.loading { padding: 28px; text-align: center; color: var(--muted); font-size: 13px; } + +.state-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--gray); flex-shrink: 0; } +.state-dot.healthy { background: var(--green); } +.state-dot.warning { background: var(--amber); } +.state-dot.critical { background: var(--red); } +.state-dot.unknown { background: var(--gray); } + +.state-label { font-size: 12px; font-weight: 500; color: var(--muted); padding: 0 2px; } +.state-label.healthy { color: var(--green); } +.state-label.warning { color: var(--amber); } +.state-label.critical { color: var(--red); } + +/* metrics */ +.metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } +.metric { background: var(--fill); border-radius: 9px; padding: 9px 11px; } +.metric-top { display: flex; align-items: baseline; justify-content: space-between; } +.metric-val { font-weight: 600; font-size: 15px; font-variant-numeric: tabular-nums; } +.bar { height: 4px; background: rgba(255, 255, 255, 0.10); border-radius: 2px; margin-top: 7px; overflow: hidden; } +.bar-fill { height: 100%; border-radius: 2px; background: var(--green); transition: width 0.35s ease; } +.bar-fill.warning { background: var(--amber); } +.bar-fill.critical { background: var(--red); } + +.offline { background: var(--fill); border-radius: 9px; padding: 14px; color: var(--amber); font-size: 13px; text-align: center; } + +.counts { display: flex; gap: 14px; font-size: 12px; color: var(--muted); padding: 2px 4px; } +.counts b { color: var(--text); font-variant-numeric: tabular-nums; } + +.finding { display: flex; align-items: center; gap: 8px; font-size: 12px; background: var(--fill); border-radius: 8px; padding: 8px 10px; } +.fdot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } +.finding.warning .fdot { background: var(--amber); } +.finding.critical .fdot { background: var(--red); } +.finding.info .fdot { background: var(--blue); } + +.app-row { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 8px; border-radius: 6px; } +.app-row:hover { background: var(--fill); } +.list.tight { gap: 0; } +.app-name { flex: 1; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.more { background: none; border: none; color: var(--blue); font-size: 11px; cursor: pointer; padding: 5px 8px; text-align: left; } +.more:hover { opacity: 0.7; } +.app-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--gray); flex-shrink: 0; } +.app-dot.running { background: var(--green); } +.app-dot.stopped { background: var(--gray); } +.app-dot.restarting, .app-dot.unhealthy { background: var(--amber); } + +.pop-foot { display: flex; gap: 7px; padding-top: 7px; border-top: 0.5px solid var(--hairline); } +.btn { + flex: 1; padding: 7px 12px; + background: var(--fill); color: var(--text); + border: none; border-radius: 8px; font-size: 12px; font-weight: 500; + cursor: pointer; transition: background 0.12s ease; +} +.btn:hover { background: var(--fill-hover); } +.btn.primary { background: var(--blue); color: #fff; } +.btn.primary:hover { background: #2b95ff; } +.btn.ghost { flex: 0 0 auto; color: var(--muted); } +.btn.icon { padding: 7px 11px; } +.btn:disabled { opacity: 0.5; cursor: default; } + +/* compact back bar */ +.backbar { padding-bottom: 6px; } +.back { font-size: 16px; line-height: 1; padding: 0 4px 0 0; } + +/* ---------- Main window: AppKit source-list + detail ---------- */ +.win { display: flex; height: 100vh; overflow: hidden; } + +/* vibrant source list (sidebar material shows through) */ +.sidebar { + width: 220px; flex-shrink: 0; + display: flex; flex-direction: column; + background: transparent; + border-right: 0.5px solid var(--hairline); + padding: 0 10px 10px; + overflow-y: auto; +} +.side-drag { height: 38px; flex-shrink: 0; } /* drag region under traffic lights */ +.side-group-title { + font-size: 11px; font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase; + color: var(--faint); padding: 4px 8px 6px; +} +.side-list { display: flex; flex-direction: column; gap: 1px; } +.side-row { + display: flex; align-items: center; gap: 8px; + padding: 6px 8px; border: none; border-radius: 6px; + background: transparent; color: var(--text); cursor: default; + text-align: left; transition: background 0.12s ease; +} +.side-row:hover { background: var(--fill); } +.side-row.active { background: var(--sel); } +.side-name { flex: 1; font-weight: 500; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.side-current { width: 6px; height: 6px; border-radius: 50%; background: var(--blue); flex-shrink: 0; } +.side-empty { padding: 10px 8px; font-size: 12px; color: var(--muted); } +.side-empty.err { color: var(--amber); } + +/* detail pane — more opaque so it reads as content, not sidebar */ +.detail { + flex: 1; min-width: 0; + display: flex; flex-direction: column; + background: rgba(28, 28, 30, 0.55); + overflow: hidden; +} +.detail-empty { flex: 1; display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 14px; } + +/* window toolbar (unified title bar area) */ +.toolbar { + display: flex; align-items: center; gap: 10px; + height: 52px; flex-shrink: 0; + padding: 0 18px; padding-top: 6px; + border-bottom: 0.5px solid var(--hairline); +} +.toolbar-title { display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0; } +.toolbar-title h1 { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; } +.toolbar-host { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +.detail-body { flex: 1; overflow-y: auto; padding: 18px; display: flex; flex-direction: column; gap: 20px; } + +/* metric tiles */ +.tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; } +.tile { + background: rgba(255, 255, 255, 0.05); + border: 0.5px solid var(--hairline); + border-radius: 11px; padding: 13px 14px; +} +.tile-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.02em; color: var(--muted); } +.tile-val { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; margin-top: 4px; font-variant-numeric: tabular-nums; } +.tile-sub { font-size: 11px; color: var(--faint); margin-top: 4px; font-variant-numeric: tabular-nums; } +.tile .bar { margin-top: 9px; } + +/* grouped list (macOS inset group) */ +.group { display: flex; flex-direction: column; gap: 7px; } +.group-title { + display: flex; align-items: baseline; gap: 8px; + font-size: 12px; font-weight: 600; color: var(--muted); + text-transform: uppercase; letter-spacing: 0.02em; padding: 0 2px; +} +.group-count { margin-left: auto; text-transform: none; font-weight: 500; color: var(--faint); font-variant-numeric: tabular-nums; } +.group-box { + background: rgba(255, 255, 255, 0.04); + border: 0.5px solid var(--hairline); + border-radius: 11px; overflow: hidden; +} +.group-empty { padding: 16px; text-align: center; color: var(--faint); font-size: 13px; } + +.table-row { + display: flex; align-items: center; gap: 10px; + padding: 10px 14px; + border-bottom: 0.5px solid var(--hairline); +} +.table-row:last-child { border-bottom: none; } +.table-name { font-weight: 500; font-size: 13px; } +.table-sub { flex: 1; color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.table-status { font-size: 11px; font-weight: 500; text-transform: capitalize; color: var(--muted); } +.table-status.running { color: var(--green); } +.table-status.restarting, .table-status.unhealthy { color: var(--amber); } + +.finding-row { + display: flex; align-items: center; gap: 10px; + padding: 10px 14px; font-size: 13px; + border-bottom: 0.5px solid var(--hairline); +} +.finding-row:last-child { border-bottom: none; } +.finding-row.warning .fdot { background: var(--amber); } +.finding-row.critical .fdot { background: var(--red); } +.finding-row.info .fdot { background: var(--blue); } + +/* toolbar buttons */ +.tbtn { + display: inline-flex; align-items: center; gap: 5px; + height: 26px; padding: 0 11px; + background: var(--fill); border: none; border-radius: 7px; + color: var(--text); font-size: 12px; font-weight: 500; cursor: pointer; + transition: background 0.12s ease; +} +.tbtn:hover { background: var(--fill-hover); } +.tbtn.on { background: var(--blue); color: #fff; } + +/* per-app action buttons */ +.row-actions { display: flex; gap: 3px; margin-left: 10px; } +.act { + width: 24px; height: 24px; + display: inline-flex; align-items: center; justify-content: center; + background: transparent; border: none; border-radius: 6px; + color: var(--muted); font-size: 12px; cursor: pointer; + transition: background 0.12s ease, color 0.12s ease; +} +.act:hover { background: var(--fill); color: var(--text); } +.act.danger:hover { background: rgba(255, 69, 58, 0.2); color: var(--red); } + +/* row busy spinner */ +.row-spin { + width: 14px; height: 14px; margin: 0 5px; + border: 2px solid var(--fill-hover); border-top-color: var(--blue); + border-radius: 50%; display: inline-block; animation: spin 0.7s linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } + +/* "more" dropdown menu */ +.more-wrap { position: relative; display: inline-flex; } +.menu-pop { + position: absolute; top: 28px; right: 0; z-index: 20; + min-width: 178px; padding: 5px; + background: rgba(44, 44, 48, 0.98); + border: 0.5px solid var(--hairline); border-radius: 10px; + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5); + display: flex; flex-direction: column; gap: 1px; +} +.menu-pop button { + display: flex; align-items: center; gap: 9px; + padding: 7px 9px; background: none; border: none; border-radius: 6px; + color: var(--text); font-size: 13px; text-align: left; cursor: pointer; +} +.menu-pop button:hover { background: var(--fill); } +.menu-pop button.danger { color: var(--red); } +.menu-pop button.danger:hover { background: rgba(255, 69, 58, 0.18); } +.menu-pop svg { color: var(--muted); flex-shrink: 0; } +.menu-pop button.danger svg { color: var(--red); } + +/* toasts */ +.toaster { + position: fixed; bottom: 16px; right: 16px; z-index: 100; + display: flex; flex-direction: column; gap: 8px; align-items: flex-end; + pointer-events: none; +} +.toast { + padding: 9px 14px; font-size: 13px; font-weight: 500; + background: rgba(44, 44, 48, 0.98); color: var(--text); + border: 0.5px solid var(--hairline); border-radius: 9px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); + animation: toast-in 0.18s ease; +} +.toast.ok { border-left: 3px solid var(--green); } +.toast.err { border-left: 3px solid var(--red); } +.toast.info { border-left: 3px solid var(--blue); } +@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } } + +/* modals / dialogs */ +.modal-back { + position: fixed; inset: 0; z-index: 200; + background: rgba(0, 0, 0, 0.4); + display: flex; align-items: center; justify-content: center; + animation: fade-in 0.14s ease; +} +@keyframes fade-in { from { opacity: 0; } } +.modal { + width: 360px; max-width: 90vw; max-height: 82vh; + display: flex; flex-direction: column; + background: rgba(40, 40, 44, 0.98); + border: 0.5px solid var(--hairline); border-radius: 13px; + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55); + animation: modal-in 0.16s ease; +} +.modal.wide { width: 640px; } +@keyframes modal-in { from { opacity: 0; transform: scale(0.97); } } +.modal-head { + display: flex; align-items: center; justify-content: space-between; + padding: 12px 14px; border-bottom: 0.5px solid var(--hairline); +} +.modal-title { font-size: 14px; font-weight: 600; } +.modal-body { padding: 14px; overflow: auto; display: flex; flex-direction: column; gap: 12px; } +.modal-foot { + display: flex; align-items: center; gap: 8px; justify-content: flex-end; + padding: 12px 14px; border-top: 0.5px solid var(--hairline); +} +.modal-foot .btn { flex: 0 0 auto; padding: 7px 16px; } + +.dialog-text { font-size: 13px; line-height: 1.5; color: var(--text); } +.dialog-text b { font-weight: 600; } + +.btn.danger { background: var(--red); color: #fff; } +.btn.danger:hover { background: #ff5b52; } + +/* form fields */ +.field { display: flex; flex-direction: column; gap: 5px; } +.field-label { font-size: 12px; font-weight: 500; color: var(--muted); } +.input { + padding: 8px 10px; font-size: 13px; + background: rgba(0, 0, 0, 0.28); color: var(--text); + border: 0.5px solid var(--hairline); border-radius: 8px; + outline: none; +} +.input:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.25); } +.check { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text); cursor: pointer; } +.check input { width: 15px; height: 15px; accent-color: var(--blue); } + +/* logs viewer */ +.logs { + font-family: "SF Mono", Menlo, Monaco, monospace; font-size: 11.5px; line-height: 1.5; + white-space: pre-wrap; word-break: break-word; + max-height: 52vh; overflow: auto; + padding: 12px; background: rgba(0, 0, 0, 0.35); border-radius: 9px; + color: #d7d7d7; +} + +/* quick command chips */ +.quick { display: flex; flex-wrap: wrap; gap: 7px; } +.chip { + padding: 5px 12px; font-size: 12px; font-family: "SF Mono", Menlo, monospace; + background: var(--fill); border: none; border-radius: 999px; + color: var(--text); cursor: pointer; transition: background 0.12s ease; +} +.chip:hover { background: var(--fill-hover); } + +/* integrated terminal drawer (IDE style) */ +.term-drawer { + flex-shrink: 0; + height: 0; overflow: hidden; + display: flex; flex-direction: column; + border-top: 0.5px solid transparent; + background: rgba(0, 0, 0, 0.35); +} +.term-drawer.open { height: 46%; border-top-color: var(--hairline); } +.term-drawer.max { height: 100%; } +.detail.term-max .detail-body { display: none; } +.term-head { + display: flex; align-items: center; justify-content: space-between; gap: 8px; + padding: 5px 8px 0; flex-shrink: 0; + border-bottom: 0.5px solid var(--hairline); +} +.term-head-btns { display: flex; align-items: center; gap: 4px; padding-bottom: 5px; } + +/* terminal tabs */ +.term-tabs { display: flex; align-items: flex-end; gap: 3px; flex: 1; min-width: 0; overflow-x: auto; } +.term-tab { + display: flex; align-items: center; gap: 6px; + padding: 5px 9px; max-width: 160px; + background: transparent; border: none; + border-radius: 7px 7px 0 0; + color: var(--muted); font-size: 12px; cursor: pointer; + border-bottom: 2px solid transparent; +} +.term-tab:hover { background: var(--fill); color: var(--text); } +.term-tab.active { background: rgba(0, 0, 0, 0.3); color: var(--text); border-bottom-color: var(--blue); } +.tab-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background: var(--green); } +.tab-dot.container { background: var(--amber); } +.tab-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.tab-close { color: var(--faint); font-size: 10px; padding: 1px 3px; border-radius: 4px; } +.tab-close:hover { background: var(--fill-hover); color: var(--text); } +.term-add { + flex-shrink: 0; width: 24px; height: 26px; + background: transparent; border: none; color: var(--muted); + font-size: 15px; cursor: pointer; border-radius: 6px; +} +.term-add:hover:not(:disabled) { background: var(--fill); color: var(--text); } +.term-add:disabled { opacity: 0.35; cursor: default; } + +/* terminal stack (one visible at a time) */ +.term-stack { flex: 1; min-height: 0; position: relative; } +.term { position: absolute; inset: 0; padding: 4px 6px; } +.term .xterm { width: 100%; height: 100%; } +.term-hidden { display: none; } +.term .xterm { height: 100%; } +.term .xterm-viewport { background: transparent !important; } +.term-empty { + position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; + color: var(--faint); font-size: 12px; text-align: center; padding: 20px; +} diff --git a/apps/desktop/src/vite-env.d.ts b/apps/desktop/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/apps/desktop/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/apps/desktop/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/apps/desktop/tsconfig.node.json b/apps/desktop/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/apps/desktop/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts new file mode 100644 index 0000000..ddad22a --- /dev/null +++ b/apps/desktop/vite.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// @ts-expect-error process is a nodejs global +const host = process.env.TAURI_DEV_HOST; + +// https://vite.dev/config/ +export default defineConfig(async () => ({ + plugins: [react()], + + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell Vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, +})); diff --git a/cmd/neo-bridge/main.go b/cmd/neo-bridge/main.go new file mode 100644 index 0000000..36dc8ee --- /dev/null +++ b/cmd/neo-bridge/main.go @@ -0,0 +1,663 @@ +// neo-bridge is a machine-facing helper for the Neo Desktop app. It exposes +// read-only Neo operations as one-shot commands: +// +// neo-bridge [paramsJSON] +// +// It prints a single JSON object to stdout and exits. Human-readable diagnostics +// go to stderr so they never corrupt the protocol. The desktop app calls this +// per poll; it reuses the same ~/.neo/config.json, SSH, and remote state as the +// CLI, so it never needs an external `neo` binary or a monitoring agent. +package main + +import ( + "encoding/json" + "fmt" + "net" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + cryptossh "golang.org/x/crypto/ssh" + + "github.com/vxero/neo/internal/config" + "github.com/vxero/neo/internal/remote" + "github.com/vxero/neo/internal/ssh" + "github.com/vxero/neo/internal/state" +) + +const protocolVersion = 1 + +// version is stamped at build time via -ldflags "-X main.version=...". +var version = "0.1.0-dev" + +func main() { + if len(os.Args) < 2 { + fail("invalid_request", "usage: neo-bridge [paramsJSON]") + } + method := os.Args[1] + + // `pty` is a raw interactive-shell mode (not the JSON protocol): it bridges + // stdio to a remote PTY over neo's SSH auth. Used by the desktop terminal. + if method == "pty" { + runInteractivePty(os.Args[2:]) + return + } + + var params map[string]any + if len(os.Args) > 2 && strings.TrimSpace(os.Args[2]) != "" { + if err := json.Unmarshal([]byte(os.Args[2]), ¶ms); err != nil { + fail("invalid_request", "bad params JSON: "+err.Error()) + } + } + + var ( + result any + err error + ) + switch method { + case "bridge.hello": + result = hello() + case "server.list": + result, err = listServers() + case "server.snapshot": + result, err = snapshot(str(params, "server")) + case "app.list": + result, err = listApps(str(params, "server")) + case "diagnostics.run": + result, err = diagnostics(str(params, "server")) + case "app.action": + result, err = appAction(str(params, "server"), str(params, "app"), str(params, "action")) + case "app.logs": + result, err = appLogs(str(params, "server"), str(params, "app")) + case "app.domain": + result, err = appDomain(str(params, "server"), str(params, "app"), str(params, "domain"), boolp(params, "https")) + case "server.sshkey": + result, err = serverSSHKey(str(params, "server")) + default: + fail("invalid_request", "unknown method: "+method) + } + if err != nil { + fail("internal_error", err.Error()) + } + emit(result) +} + +// ---- methods ---- + +type helloResult struct { + ProtocolVersion int `json:"protocolVersion"` + BridgeVersion string `json:"bridgeVersion"` + CliCore string `json:"cliCore"` + Platform string `json:"platform"` + Activated bool `json:"activated"` +} + +func hello() helloResult { + cfg, _ := config.Load() + activated := cfg != nil && cfg.LicenseKey != "" + return helloResult{ + ProtocolVersion: protocolVersion, + BridgeVersion: version, + CliCore: version, + Platform: runtime.GOOS + "/" + runtime.GOARCH, + Activated: activated, + } +} + +type serverSummary struct { + Name string `json:"name"` + Host string `json:"host"` + Port int `json:"port"` + KeyPath string `json:"keyPath"` + Current bool `json:"current"` +} + +func listServers() ([]serverSummary, error) { + cfg, err := config.Load() + if err != nil { + return nil, err + } + out := make([]serverSummary, 0, len(cfg.Servers)) + for _, s := range cfg.ServerList() { + port := s.Port + if port == 0 { + port = 22 + } + out = append(out, serverSummary{ + Name: s.Name, + Host: s.Host, + Port: port, + KeyPath: s.Key, + Current: s.Name == cfg.Current, + }) + } + return out, nil +} + +type workloadCounts struct { + Total int `json:"total"` + Running int `json:"running"` +} + +type serverSnapshot struct { + Server string `json:"server"` + Reachable bool `json:"reachable"` + ObservedAt string `json:"observedAt"` + LatencyMS int64 `json:"latencyMs"` + CPUPercent float64 `json:"cpuPercent"` + RAMUsedBytes uint64 `json:"ramUsedBytes"` + RAMTotalBytes uint64 `json:"ramTotalBytes"` + DiskUsedBytes uint64 `json:"diskUsedBytes"` + DiskTotalBytes uint64 `json:"diskTotalBytes"` + UptimeSeconds uint64 `json:"uptimeSeconds"` + Apps workloadCounts `json:"apps"` + Services workloadCounts `json:"services"` +} + +func snapshot(name string) (*serverSnapshot, error) { + srv, err := resolve(name) + if err != nil { + return nil, err + } + snap := &serverSnapshot{Server: srv.Name, ObservedAt: now()} + + start := time.Now() + exec, err := connect(srv) + if err != nil { + // Unreachable is a normal state, not an error. + return snap, nil + } + defer exec.Close() + snap.Reachable = true + snap.LatencyMS = time.Since(start).Milliseconds() + + // CPU is measured as the busy fraction across two /proc/stat samples ~0.3s + // apart. A single `top -bn1` reports an instantaneous, unreliable value + // (often ~0 idle → 100%); two samples track what htop shows. + cpuCmd := `CPU=$(set -- $(grep "^cpu " /proc/stat); t1=0; for v in $2 $3 $4 $5 $6 $7 $8 $9; do t1=$((t1+v)); done; i1=$(($5+$6)); ` + + `sleep 0.3; set -- $(grep "^cpu " /proc/stat); t2=0; for v in $2 $3 $4 $5 $6 $7 $8 $9; do t2=$((t2+v)); done; i2=$(($5+$6)); ` + + `dt=$((t2-t1)); di=$((i2-i1)); if [ "$dt" -gt 0 ]; then echo $(( (100*(dt-di))/dt )); else echo 0; fi)` + metricsCmd := cpuCmd + `; echo "CPU:${CPU:-0}"; ` + + `echo "MEM:$(free -b 2>/dev/null | awk '/Mem:/{printf "%d/%d",$3,$2}' || echo 0/0)"; ` + + `echo "DISK:$(df -B1 / 2>/dev/null | awk 'NR==2{printf "%d/%d",$3,$2}' || echo 0/0)"; ` + + `echo "UP:$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0)"` + if out, err := exec.Run(metricsCmd); err == nil { + for _, line := range strings.Split(strings.TrimSpace(out), "\n") { + line = strings.TrimSpace(line) + switch { + case strings.HasPrefix(line, "CPU:"): + snap.CPUPercent = parseFloat(strings.TrimPrefix(line, "CPU:")) + case strings.HasPrefix(line, "MEM:"): + snap.RAMUsedBytes, snap.RAMTotalBytes = parsePair(strings.TrimPrefix(line, "MEM:")) + case strings.HasPrefix(line, "DISK:"): + snap.DiskUsedBytes, snap.DiskTotalBytes = parsePair(strings.TrimPrefix(line, "DISK:")) + case strings.HasPrefix(line, "UP:"): + snap.UptimeSeconds = parseUint(strings.TrimPrefix(line, "UP:")) + } + } + } + + if st, err := state.Load(exec); err == nil { + for _, a := range st.Apps { + snap.Apps.Total++ + if a.Status == "running" { + snap.Apps.Running++ + } + } + for _, s := range st.Services { + snap.Services.Total++ + if s.Status == "running" { + snap.Services.Running++ + } + } + } + return snap, nil +} + +type appSummary struct { + Name string `json:"name"` + Domain string `json:"domain"` + Status string `json:"status"` + Image string `json:"image"` +} + +func listApps(name string) ([]appSummary, error) { + srv, err := resolve(name) + if err != nil { + return nil, err + } + exec, err := connect(srv) + if err != nil { + return []appSummary{}, nil + } + defer exec.Close() + st, err := state.Load(exec) + if err != nil { + return []appSummary{}, nil + } + out := make([]appSummary, 0, len(st.Apps)) + for _, a := range st.Apps { + status := a.Status + if status == "" { + status = "stopped" + } + out = append(out, appSummary{Name: a.Name, Domain: a.Domain, Status: status, Image: a.Image}) + } + return out, nil +} + +type finding struct { + ID string `json:"id"` + Rule string `json:"rule"` + Severity string `json:"severity"` + Summary string `json:"summary"` + LastObservedAt string `json:"lastObservedAt"` +} + +func diagnostics(name string) ([]finding, error) { + snap, err := snapshot(name) + if err != nil { + return nil, err + } + out := []finding{} + add := func(rule, sev, summary string) { + out = append(out, finding{ID: rule, Rule: rule, Severity: sev, Summary: summary, LastObservedAt: now()}) + } + if !snap.Reachable { + add("reachability", "critical", snap.Server+" is unreachable") + return out, nil + } + if snap.DiskTotalBytes > 0 { + p := pct(snap.DiskUsedBytes, snap.DiskTotalBytes) + if p >= 90 { + add("disk", "critical", fmt.Sprintf("Disk at %d%%", p)) + } else if p >= 75 { + add("disk", "warning", fmt.Sprintf("Disk at %d%%", p)) + } + } + if snap.RAMTotalBytes > 0 { + p := pct(snap.RAMUsedBytes, snap.RAMTotalBytes) + if p >= 95 { + add("ram", "critical", fmt.Sprintf("RAM at %d%%", p)) + } else if p >= 80 { + add("ram", "warning", fmt.Sprintf("RAM at %d%%", p)) + } + } + if snap.CPUPercent >= 95 { + add("cpu", "critical", fmt.Sprintf("CPU at %.0f%%", snap.CPUPercent)) + } else if snap.CPUPercent >= 80 { + add("cpu", "warning", fmt.Sprintf("CPU at %.0f%%", snap.CPUPercent)) + } + if snap.Apps.Total > snap.Apps.Running { + add("app_state", "warning", fmt.Sprintf("%d app(s) not running", snap.Apps.Total-snap.Apps.Running)) + } + return out, nil +} + +type actionResult struct { + OK bool `json:"ok"` + Status string `json:"status"` +} + +// appAction performs a lifecycle op (restart/stop/start) directly via Docker +// over SSH — no terminal, no external neo binary — and updates remote state. +func appAction(server, app, action string) (*actionResult, error) { + if app == "" { + return nil, fmt.Errorf("app is required") + } + srv, err := resolve(server) + if err != nil { + return nil, err + } + exec, err := connect(srv) + if err != nil { + return nil, err + } + defer exec.Close() + + d := remote.NewDocker(exec) + container := config.AppContainer(app) + switch action { + case "restart": + err = d.Restart(container) + case "stop": + err = d.Stop(container) + case "start": + err = d.Start(container) + case "remove": + _ = d.Stop(container) + err = d.Remove(container) + default: + return nil, fmt.Errorf("unknown action: %s", action) + } + if err != nil { + return nil, err + } + + // Remove: also drop the proxy route and state entry (volumes are kept). + if action == "remove" { + _ = remote.NewCaddy(exec).RemoveRoute(app) + if st, e := state.Load(exec); e == nil { + delete(st.Apps, app) + _ = state.Save(exec, st) + } + return &actionResult{OK: true, Status: "removed"}, nil + } + + status := "running" + if action == "stop" { + status = "stopped" + } + if st, e := state.Load(exec); e == nil { + if a, ok := st.Apps[app]; ok { + a.Status = status + st.Apps[app] = a + _ = state.Save(exec, st) + } + } + return &actionResult{OK: true, Status: status}, nil +} + +type logsResult struct { + Logs string `json:"logs"` +} + +// appLogs returns the last chunk of a container's logs (non-following, so it +// never hangs). Powers the GUI log viewer. +func appLogs(server, app string) (*logsResult, error) { + if app == "" { + return nil, fmt.Errorf("app is required") + } + srv, err := resolve(server) + if err != nil { + return nil, err + } + exec, err := connect(srv) + if err != nil { + return nil, err + } + defer exec.Close() + container := config.AppContainer(app) + cmd := fmt.Sprintf("docker logs --tail 500 %s 2>&1 | tail -c 80000", container) + out, err := exec.Run(cmd) + if err != nil && out == "" { + return nil, err + } + return &logsResult{Logs: out}, nil +} + +// appDomain assigns a domain to an app via Caddy (HTTPS auto-provisioned unless +// https=false) and records it in remote state. No terminal, no neo binary. +func appDomain(server, app, domain string, https bool) (*actionResult, error) { + if app == "" || domain == "" { + return nil, fmt.Errorf("app and domain are required") + } + srv, err := resolve(server) + if err != nil { + return nil, err + } + exec, err := connect(srv) + if err != nil { + return nil, err + } + defer exec.Close() + + st, err := state.Load(exec) + if err != nil { + return nil, err + } + a, ok := st.Apps[app] + if !ok { + return nil, fmt.Errorf("app not found: %s", app) + } + port := a.InternalPort + if port == 0 { + port = 80 + } + upstream := fmt.Sprintf("%s:%d", config.AppContainer(app), port) + + caddy := remote.NewCaddy(exec) + domains := []string{domain} + if https { + err = caddy.UpdateRoute(app, domains, upstream) + } else { + err = caddy.UpdateRouteHTTP(app, domains, upstream) + } + if err != nil { + return nil, err + } + + a.Domain = domain + a.HTTPOnly = !https + st.Apps[app] = a + _ = state.Save(exec, st) + return &actionResult{OK: true, Status: domain}, nil +} + +type sshKeyResult struct { + KeyPath string `json:"keyPath"` +} + +// serverSSHKey finds the private key that actually authenticates to a server. +// The user may have dozens of keys; system `ssh` only tries id_* by default, +// so the desktop terminal needs the exact one. Each key is probed in isolation +// (single auth method) so the result is the real match, not a false positive. +func serverSSHKey(name string) (*sshKeyResult, error) { + srv, err := resolve(name) + if err != nil { + return nil, err + } + if srv.Key != "" { + return &sshKeyResult{KeyPath: srv.Key}, nil + } + + user, host := splitHost(srv.Host) + port := srv.Port + if port == 0 { + port = 22 + } + addr := net.JoinHostPort(host, strconv.Itoa(port)) + + home, err := os.UserHomeDir() + if err != nil { + return &sshKeyResult{}, nil + } + dir := filepath.Join(home, ".ssh") + ents, _ := os.ReadDir(dir) + + skip := map[string]bool{"known_hosts": true, "config": true, "authorized_keys": true, "agent": true} + // Probe neo's managed key first (this is what `neo init` installs), then + // id_ed25519 / id_rsa, then everything else. + files := []string{ + ssh.NeoKeyPath(), + filepath.Join(dir, "id_ed25519"), + filepath.Join(dir, "id_rsa"), + } + for _, e := range ents { + if e.IsDir() { + continue + } + n := e.Name() + if n == "id_ed25519" || n == "id_rsa" || skip[n] { + continue + } + if strings.HasSuffix(n, ".pub") || strings.HasSuffix(n, ".ppk") || strings.HasSuffix(n, ".gz") { + continue + } + files = append(files, filepath.Join(dir, n)) + } + + for _, kp := range files { + data, err := os.ReadFile(kp) + if err != nil { + continue + } + signer, err := cryptossh.ParsePrivateKey(data) + if err != nil { + continue // encrypted or not a key + } + cfg := &cryptossh.ClientConfig{ + User: user, + Auth: []cryptossh.AuthMethod{cryptossh.PublicKeys(signer)}, + HostKeyCallback: cryptossh.InsecureIgnoreHostKey(), + Timeout: 6 * time.Second, + } + conn, err := cryptossh.Dial("tcp", addr, cfg) + if err == nil { + _ = conn.Close() + return &sshKeyResult{KeyPath: kp}, nil + } + } + return &sshKeyResult{}, nil +} + +func splitHost(h string) (string, string) { + if i := strings.IndexByte(h, '@'); i >= 0 { + return h[:i], h[i+1:] + } + return "root", h +} + +// runInteractivePty connects to a server (neo auth) and bridges stdio to a +// remote login shell, or to `docker exec -it sh` when a container +// is given. Args: [cols] [rows]. +func runInteractivePty(args []string) { + if len(args) < 1 { + fmt.Fprintln(os.Stderr, "usage: neo-bridge pty [cols] [rows]") + os.Exit(2) + } + server := args[0] + container := "" + if len(args) > 1 && args[1] != "-" && args[1] != "" { + container = args[1] + } + cols := argInt(args, 2, 80) + rows := argInt(args, 3, 24) + + srv, err := resolve(server) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + exec, err := connect(srv) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot connect to %s: %v\r\n", srv.Host, err) + os.Exit(1) + } + defer exec.Close() + + cmd := "" + if container != "" { + cmd = fmt.Sprintf("docker exec -it %s sh", config.AppContainer(container)) + } + if err := exec.InteractiveShell(cols, rows, cmd); err != nil { + fmt.Fprintf(os.Stderr, "\r\nsession ended: %v\r\n", err) + os.Exit(1) + } +} + +func argInt(args []string, i, def int) int { + if i >= len(args) { + return def + } + if n, err := strconv.Atoi(strings.TrimSpace(args[i])); err == nil && n > 0 { + return n + } + return def +} + +// ---- helpers ---- + +func resolve(name string) (*config.Server, error) { + cfg, err := config.Load() + if err != nil { + return nil, err + } + if name == "" { + name = cfg.Current + } + s, ok := cfg.Servers[name] + if !ok { + fail("server_not_found", "server not found: "+name) + } + return &s, nil +} + +func connect(srv *config.Server) (*ssh.Executor, error) { + exec := ssh.New(srv.Host, srv.Port) + exec.NonInteractive = true // never prompt for password / host trust + if srv.Key != "" { + if data, err := os.ReadFile(srv.Key); err == nil { + exec.PrivateKey = data + } + } + if err := exec.Connect(); err != nil { + return nil, err + } + return exec, nil +} + +func now() string { return time.Now().UTC().Format(time.RFC3339) } + +func pct(used, total uint64) int { + if total == 0 { + return 0 + } + return int(used * 100 / total) +} + +func parseFloat(s string) float64 { + f, _ := strconv.ParseFloat(strings.TrimSpace(s), 64) + if f < 0 { + f = 0 + } + return f +} + +func parseUint(s string) uint64 { + u, _ := strconv.ParseUint(strings.TrimSpace(s), 10, 64) + return u +} + +func parsePair(s string) (uint64, uint64) { + parts := strings.SplitN(strings.TrimSpace(s), "/", 2) + if len(parts) != 2 { + return 0, 0 + } + return parseUint(parts[0]), parseUint(parts[1]) +} + +func boolp(m map[string]any, key string) bool { + if m == nil { + return false + } + b, _ := m[key].(bool) + return b +} + +func str(m map[string]any, key string) string { + if m == nil { + return "" + } + if v, ok := m[key].(string); ok { + return v + } + return "" +} + +func emit(result any) { + enc := json.NewEncoder(os.Stdout) + _ = enc.Encode(map[string]any{"version": protocolVersion, "result": result}) +} + +func fail(code, message string) { + enc := json.NewEncoder(os.Stdout) + _ = enc.Encode(map[string]any{ + "version": protocolVersion, + "error": map[string]any{"code": code, "message": message}, + }) + os.Exit(0) // protocol-level error still exits 0; the payload carries the error +} diff --git a/internal/ssh/executor.go b/internal/ssh/executor.go index e673cb4..f47d526 100644 --- a/internal/ssh/executor.go +++ b/internal/ssh/executor.go @@ -8,6 +8,7 @@ import ( "net" "os" "path/filepath" + "strconv" "strings" "time" @@ -97,6 +98,104 @@ func (e *Executor) Close() error { return nil } +// InteractiveShell opens a PTY-backed session and bridges the local process's +// stdin/stdout/stderr to it. If command is empty it starts a login shell; +// otherwise it runs that command interactively (e.g. `docker exec -it … sh`). +// Used by `neo-bridge pty` so the desktop terminal reuses neo's working auth. +func (e *Executor) InteractiveShell(cols, rows int, command string) error { + session, err := e.client.NewSession() + if err != nil { + return fmt.Errorf("ssh session: %w", err) + } + defer session.Close() + + if cols <= 0 { + cols = 80 + } + if rows <= 0 { + rows = 24 + } + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + if err := session.RequestPty("xterm-256color", rows, cols, modes); err != nil { + return fmt.Errorf("request pty: %w", err) + } + + session.Stdout = os.Stdout + session.Stderr = os.Stderr + stdinPipe, err := session.StdinPipe() + if err != nil { + return err + } + + // Bridge stdin, intercepting resize control frames. The desktop app sends + // "\x1eRx\n" (0x1e = RS, never produced by a keyboard) to grow + // the remote PTY; everything else is forwarded as keystrokes. + go func() { + buf := make([]byte, 4096) + var ctrl []byte + inCtrl := false + for { + n, rerr := os.Stdin.Read(buf) + if n > 0 { + forward := make([]byte, 0, n) + for _, b := range buf[:n] { + switch { + case inCtrl && b == '\n': + applyResize(session, ctrl) + ctrl = ctrl[:0] + inCtrl = false + case inCtrl: + ctrl = append(ctrl, b) + case b == 0x1e: + inCtrl = true + ctrl = ctrl[:0] + default: + forward = append(forward, b) + } + } + if len(forward) > 0 { + if _, werr := stdinPipe.Write(forward); werr != nil { + return + } + } + } + if rerr != nil { + return + } + } + }() + + if command != "" { + if err := session.Start(command); err != nil { + return err + } + } else if err := session.Shell(); err != nil { + return err + } + return session.Wait() +} + +// applyResize parses a "Rx" control frame and resizes the PTY. +func applyResize(session *ssh.Session, frame []byte) { + s := string(frame) + if len(s) < 2 || s[0] != 'R' { + return + } + parts := strings.SplitN(s[1:], "x", 2) + if len(parts) != 2 { + return + } + cols, e1 := strconv.Atoi(parts[0]) + rows, e2 := strconv.Atoi(parts[1]) + if e1 == nil && e2 == nil && cols > 0 && rows > 0 { + _ = session.WindowChange(rows, cols) + } +} + // Run executes a command and returns combined stdout. func (e *Executor) Run(cmd string) (string, error) { e.debugf("run: %s", cmd) diff --git a/plans/2026-07-18-neo-desktop-tray-application.md b/plans/2026-07-18-neo-desktop-tray-application.md new file mode 100644 index 0000000..f9c7e27 --- /dev/null +++ b/plans/2026-07-18-neo-desktop-tray-application.md @@ -0,0 +1,898 @@ +# Neo Desktop Tray Application + +## Status + +- **Document type:** implementation plan +- **Date:** 2026-07-18 +- **Target platforms:** macOS and Windows +- **Repository decision:** keep the desktop application in this repository +- **Implementation order:** scaffold the desktop shell first, then connect it to shared Neo operations +- **Working product name:** Neo Desktop +- **Bundle identifier:** `dev.vxero.neo.desktop` + +## Goal + +Create a lightweight desktop application, similar in behavior to Laravel Herd, that lives in the macOS menu bar and Windows notification area. It must let a Neo user: + +1. See whether configured remote servers are reachable. +2. Check CPU, memory, disk, latency, applications, and services quickly. +3. View recent and streaming application logs. +4. Receive useful, low-noise incident notifications. +5. Run a small allowlist of safe corrective actions such as start and restart. +6. Open a larger management window when the tray popover is not enough. + +The desktop app continues Neo's existing agentless architecture. It connects to remote servers over SSH and does not require installing a new monitoring agent. + +## Non-goals for the first release + +The first beta will not include: + +- Deploying a local project. +- Editing environment variables or displaying unmasked secrets. +- Restoring backups. +- Interactive database consoles. +- Arbitrary remote shell execution. +- Firewall, Caddy DNS, or destructive server configuration changes. +- AI-generated fixes or uploading server logs to an AI service. +- Linux desktop packaging. +- Cloud synchronization of server configuration. + +These can be added after the read-only monitoring and safe-action foundation is reliable. + +## Architecture decisions + +### 1. Keep the desktop app in the Neo monorepo + +Create `apps/desktop/` in this repository, but give it its own frontend and Rust dependencies. Do not make it a nested Git repository. + +Reasons: + +- CLI and desktop changes can be reviewed and merged atomically. +- The desktop bridge can use the existing Go `internal/` packages safely. +- Existing SSH, state, configuration, licensing, sandbox, and test infrastructure remains the source of truth. +- The bundled bridge and desktop UI can always be built from the same commit. +- The CLI and desktop can still have independent tags and release workflows. + +Consider splitting `apps/desktop` into a separate repository only after the bridge protocol is stable and one of these becomes true: + +- Desktop and CLI are maintained by different teams. +- The desktop has a substantially different release cadence. +- Other consumers need a supported public Neo SDK or API. + +### 2. Use Tauri 2 for the desktop shell + +Use Tauri 2 with React, TypeScript, and Vite. + +Tauri owns: + +- The native tray/menu-bar icon and menu. +- The small attached popover and larger management window. +- Application lifecycle and single-instance behavior. +- Start-at-login and desktop notifications. +- Bundling and launching the Go bridge sidecar. +- Signed automatic updates. +- macOS and Windows installers. + +Keep Rust code thin. It should supervise the sidecar, expose a small set of typed commands to the frontend, and implement OS integration. It must not duplicate Neo's SSH or server-management logic. + +Do not use Wails 3 for the first production release while the upstream project describes it as alpha. Revisit Wails after it reaches a stable release if removing the Rust/Go sidecar boundary would materially reduce maintenance. + +### 3. Bundle a Go `neo-bridge` sidecar + +Create `cmd/neo-bridge`. Tauri bundles a platform-specific build of this binary inside the application. + +The desktop application must not depend on an externally installed `neo` executable. GUI applications do not reliably inherit a user's shell `PATH`, and an external CLI could be a different version from the desktop UI. + +The bridge and CLI share Go application services. The bridge provides a stable machine-facing protocol; Cobra commands remain human-facing adapters. + +### 4. Reuse the existing Neo configuration + +The bridge reads and writes the existing `~/.neo/config.json` through `internal/config`. + +The desktop app must not create a second server registry. A server added by the CLI appears in the desktop app after refresh, and a server added by the desktop app must be usable by the CLI. + +Desktop-only preferences, such as polling interval and notification settings, are stored separately in the platform application-data directory. SSH hosts, keys, and the current server remain in Neo's existing configuration. + +### 5. Keep the frontend outside the trust boundary + +The React frontend never receives unrestricted shell access and never constructs SSH commands. + +The only permitted path is: + +```text +React UI + | + | typed Tauri commands/events + v +Tauri Rust shell + | + | versioned newline-delimited JSON over stdio + v +neo-bridge + | + | shared Go operations + v +SSH -> remote Neo server +``` + +All state-changing bridge methods use an explicit allowlist and validate server, application, and action identifiers. + +## Target repository layout + +```text +neo/ +|-- apps/ +| `-- desktop/ +| |-- README.md +| |-- package.json +| |-- package-lock.json +| |-- tsconfig.json +| |-- vite.config.ts +| |-- index.html +| |-- src/ +| | |-- main.tsx +| | |-- app/ +| | |-- components/ +| | |-- features/ +| | | |-- apps/ +| | | |-- diagnostics/ +| | | |-- logs/ +| | | `-- servers/ +| | |-- lib/ +| | | |-- desktop-api.ts +| | | `-- protocol.ts +| | |-- styles/ +| | `-- test/ +| `-- src-tauri/ +| |-- Cargo.toml +| |-- build.rs +| |-- capabilities/ +| |-- icons/ +| |-- src/ +| | |-- bridge.rs +| | |-- commands.rs +| | |-- lib.rs +| | |-- main.rs +| | `-- tray.rs +| |-- binaries/ +| `-- tauri.conf.json +|-- cmd/ +| |-- neo/ +| `-- neo-bridge/ +| `-- main.go +|-- internal/ +| |-- operations/ +| | |-- actions.go +| | |-- diagnostics.go +| | |-- logs.go +| | |-- service.go +| | |-- snapshot.go +| | |-- types.go +| | `-- operations_test.go +| |-- config/ +| |-- remote/ +| |-- ssh/ +| `-- state/ +|-- commands/ +|-- test/ +|-- Makefile +`-- .github/workflows/ + |-- desktop-ci.yml + `-- desktop-release.yml +``` + +Do not add Tauri, Rust, or frontend dependencies to the root Go module. The existing CLI remains buildable using its current Go and Docker workflow. + +## Phase 1: scaffold first + +The first implementation slice creates a runnable tray application before refactoring CLI behavior. This gives the team a visible walking skeleton and validates macOS/Windows tray behavior early. + +### Scaffold commands + +Use the official Tauri 2 React/TypeScript template and npm so contributors only need Node and Rust, without requiring another package manager: + +```bash +mkdir -p apps +cd apps +npm create tauri-app@latest desktop -- \ + --template react-ts \ + --manager npm +``` + +If the installed generator does not accept `--manager`, run the interactive form +`npm create tauri-app@latest` and select project name `desktop`, TypeScript/JavaScript, +npm, React, and TypeScript. Confirm that the generated dependencies are Tauri 2 +before normalizing or committing the scaffold. + +Treat the generated output as a starting point. Normalize its product name, identifier, scripts, and directory structure before committing. + +### Initial Tauri configuration + +Configure: + +- Product name: `Neo Desktop` +- Identifier: `dev.vxero.neo.desktop` +- Small tray window: approximately 380 x 560 pixels. +- Main management window: minimum 960 x 680 pixels. +- Small window hidden at startup. +- Closing a window hides it; it does not quit the tray process. +- Only one desktop process may run at a time. +- No dock icon on macOS when only the tray popover is open, if supported without harming the full-window experience. +- Strict Content Security Policy. +- Devtools disabled in production builds. + +Add only the plugins needed by the first beta: + +- `autostart` +- `notification` +- `process` +- `single-instance` +- `updater` + +The shell/sidecar capability is configured in Rust and restricted to the bundled `neo-bridge` executable. Do not expose a generic shell command to JavaScript. + +### Scaffold UI + +Build the first tray popover against a `DesktopAPI` interface and fixture provider. It should render without an SSH server: + +```ts +export interface DesktopAPI { + hello(): Promise; + listServers(): Promise; + getSnapshot(server: string): Promise; + listApps(server: string): Promise; + runAppAction(input: AppActionInput): Promise; + runDiagnostics(server: string): Promise; +} +``` + +The fixture implementation is used only for Storybook-style development, tests, and the first visual shell. Production builds must use the Tauri transport. + +The initial popover contains: + +- Neo logo and aggregate status. +- Server selector. +- Reachability and last-refreshed timestamp. +- CPU, RAM, disk, and latency cards. +- Application running/stopped counts. +- Up to three findings. +- Refresh and Open Dashboard buttons. +- Settings and Quit tray menu entries. + +### Phase 1 acceptance criteria + +- `npm run tauri dev` launches a single tray application on macOS. +- The tray icon opens and hides the popover reliably. +- The popover displays fixture server data. +- Open Dashboard shows the full management window. +- Closing either window leaves the tray application running. +- Quit terminates it completely. +- Light and dark system themes are usable. +- Frontend unit tests run without starting Tauri. +- A Windows CI build proves the scaffold compiles before real server logic is added. + +## Phase 2: introduce the bridge walking skeleton + +### Bridge process behavior + +`neo-bridge` is a long-running child process owned by Tauri. + +On startup it: + +1. Configures structured logging to stderr. +2. Reads newline-delimited JSON requests from stdin. +3. Writes only protocol responses and events to stdout. +4. Handles graceful shutdown when stdin closes or it receives a shutdown request. +5. Never prompts on stdin for passwords, host trust, license activation, or selections. + +Human-readable diagnostics go to stderr so they cannot corrupt the protocol stream. + +### Protocol envelope + +Start with protocol version `1`. + +Request: + +```json +{"version":1,"id":"req-123","method":"server.snapshot","params":{"server":"production"}} +``` + +Success response: + +```json +{"version":1,"id":"req-123","result":{"reachable":true,"latencyMs":84}} +``` + +Error response: + +```json +{ + "version":1, + "id":"req-123", + "error":{ + "code":"ssh_unreachable", + "message":"Could not connect to production", + "retryable":true, + "details":{} + } +} +``` + +Streaming event: + +```json +{"version":1,"event":"logs.line","subscription":"log-45","data":{"line":"..."}} +``` + +### Initial methods + +Implement in this order: + +| Method | Purpose | Mutates remote state | +|---|---|---:| +| `bridge.hello` | Protocol, bridge, CLI-core, platform, and activation information | No | +| `bridge.shutdown` | Graceful process shutdown | No | +| `server.list` | Read configured servers and current selection | No | +| `server.snapshot` | Reachability, metrics, and counts | No | +| `app.list` | Applications, workers, sidecars, and services | No | +| `logs.subscribe` | Start recent/live log stream | No | +| `logs.unsubscribe` | Cancel log stream | No | +| `diagnostics.run` | Produce deterministic findings | No | +| `app.action` | Start, stop, or restart one application | Yes | +| `operation.cancel` | Cancel an outstanding operation | No | + +### Stable error codes + +Define error codes in Go and mirror them in generated or checked TypeScript types: + +- `invalid_request` +- `protocol_mismatch` +- `not_activated` +- `server_not_found` +- `app_not_found` +- `ssh_unknown_host` +- `ssh_auth_failed` +- `ssh_unreachable` +- `remote_state_invalid` +- `operation_timeout` +- `operation_cancelled` +- `action_not_allowed` +- `internal_error` + +The UI makes decisions using error codes, never by parsing English error messages. + +### Bridge supervision + +The Tauri layer: + +- Starts exactly one bridge process. +- Performs `bridge.hello` before showing live data. +- Rejects an incompatible protocol version. +- Correlates responses using request IDs. +- Routes streaming events to the correct frontend window. +- Restarts the bridge at most three times after unexpected exits, using exponential backoff. +- Shows a clear error after the restart budget is exhausted. +- Terminates the bridge when the desktop app exits. + +### Phase 2 acceptance criteria + +- A bundled bridge responds to `bridge.hello` on macOS and Windows. +- The desktop lists servers from the real `~/.neo/config.json`. +- No external Neo CLI installation is required. +- Protocol stdout remains valid when debug logging is enabled. +- The desktop displays structured activation, unknown-host, auth, and unreachable errors. +- Tauri can recover from one forced bridge crash. + +## Phase 3: extract shared Go operations + +The bridge must not call Cobra commands or capture terminal output. Refactor business behavior out of `commands/` into `internal/operations` while preserving existing CLI output and flags. + +### Service dependencies + +Use dependency injection so unit tests do not require a live SSH server: + +```go +type Executor interface { + Run(ctx context.Context, command string) (string, error) + Stream(ctx context.Context, command string, output io.Writer) error + ReadFileElevated(ctx context.Context, path string) ([]byte, error) + Close() error +} + +type Connector interface { + Connect(ctx context.Context, server config.Server) (Executor, error) +} + +type Service struct { + configStore ConfigStore + connector Connector + clock Clock +} +``` + +If changing `internal/ssh.Executor` to accept contexts is too disruptive for the first slice, add context-aware wrapper methods and migrate callers incrementally. Every bridge operation still needs a deadline and cancellation path. + +### Domain types + +Use typed numeric fields in the shared operation layer rather than the current human-oriented strings: + +```go +type Snapshot struct { + Server ServerSummary `json:"server"` + Reachable bool `json:"reachable"` + ObservedAt time.Time `json:"observedAt"` + LatencyMS int64 `json:"latencyMs"` + CPUPercent float64 `json:"cpuPercent"` + RAMUsedBytes uint64 `json:"ramUsedBytes"` + RAMTotalBytes uint64 `json:"ramTotalBytes"` + DiskUsedBytes uint64 `json:"diskUsedBytes"` + DiskTotalBytes uint64 `json:"diskTotalBytes"` + UptimeSeconds uint64 `json:"uptimeSeconds"` + Apps WorkloadCounts `json:"apps"` + Services WorkloadCounts `json:"services"` + Containers []ContainerStat `json:"containers"` +} +``` + +Keep the existing CLI `neo status --json` response backward compatible. The CLI adapter maps the shared typed snapshot to the current JSON shape until a deliberate CLI schema-version change is released. + +### Snapshot collection + +Reduce SSH overhead by collecting VM metrics and Docker information in as few remote commands as practical. Requirements: + +- A default 12-second connection deadline. +- A default 15-second snapshot deadline. +- Partial Docker-stat failures do not discard valid server metrics. +- Missing platform commands produce unavailable fields, not misleading zero values. +- Raw remote output is never sent directly to the UI without parsing and validation. + +### Lifecycle actions + +Move the reusable parts of start, stop, and restart from `commands/manage.go` into the operation service. + +Each action returns a structured result: + +```go +type OperationResult struct { + OperationID string `json:"operationId"` + Status string `json:"status"` + StartedAt time.Time `json:"startedAt"` + FinishedAt *time.Time `json:"finishedAt,omitempty"` + Summary string `json:"summary"` + Changes []Change `json:"changes"` +} +``` + +The service validates application names against remote Neo state before building container commands. Never interpolate a frontend-provided container name directly into a shell command. + +### Log streaming + +The bridge owns log-stream cancellation and backpressure: + +- Default to the most recent 200 lines. +- Cap a requested tail at 5,000 lines. +- Permit follow mode. +- Limit each desktop process to five simultaneous log subscriptions. +- Batch high-volume events before sending them to the webview. +- Stop the SSH stream when the window closes, the user unsubscribes, or the context expires. +- Keep existing server-side grep behavior out of the first desktop beta; search loaded lines locally. + +### Licensing + +Move the command pre-run licensing decision into a small reusable guard so CLI and bridge enforce the same activation rules. + +`bridge.hello` returns activation status without exposing the license key. If activation is missing, the first beta may direct the user to `neo activate`; a later desktop slice can implement the existing email/key activation flow through shared Go services. + +The bridge must never log or return the license key. + +### Phase 3 acceptance criteria + +- CLI commands and JSON output remain backward compatible. +- CLI and bridge use the same snapshot, listing, log, and lifecycle implementation. +- Unit tests use fake connectors/executors. +- Existing `go test ./...` passes. +- Existing sandbox tests pass. +- Context cancellation stops live logs and long-running SSH operations. +- No Cobra, Huh, Lipgloss, or terminal UI package is imported by `internal/operations`. + +## Phase 4: live tray behavior + +### Polling policy + +The desktop polls configured servers without overwhelming SSH: + +- Immediately refresh the selected server when the popover opens. +- Refresh the selected server every 30 seconds while a window is visible. +- Refresh other configured servers every 120 seconds. +- Limit concurrent SSH snapshots to three. +- Add up to 10% random jitter so many desktop clients do not poll together. +- Back off an unreachable server through 30, 60, 120, and 300 seconds. +- Manual refresh bypasses backoff once, but repeated clicks are debounced. +- Cache the last successful snapshot and display its age when the server is offline. + +Only one layer owns periodic refresh. Prefer the desktop application service rather than allowing every React component to start its own timer. + +### Tray state + +Aggregate all configured server results into four states: + +| State | Tray appearance | Meaning | +|---|---|---| +| Healthy | Green | All recently checked servers are reachable with no critical finding | +| Warning | Amber | One or more advisories or stopped workloads | +| Critical | Red | Unreachable server or critical diagnostic | +| Unknown | Gray | No configured server, startup, stale cache, or refresh in progress | + +On macOS, use a template icon that follows light/dark menu-bar appearance. Do not rely only on color; the icon shape or badge must distinguish critical and unknown states. + +### Notifications + +Notify only on transitions: + +- Server changed from reachable to unreachable. +- Server recovered. +- Application changed to an unexpected stopped/unhealthy state. +- Critical resource threshold persisted for the configured number of samples. +- A user-triggered action succeeded or failed after the popover was closed. + +Deduplicate repeated notifications and apply a default five-minute cooldown per finding. Do not notify for the first observation after application startup until the initial scan completes. + +### Phase 4 acceptance criteria + +- Polling continues while windows are hidden. +- Opening several windows does not multiply polling. +- Last-known data is clearly marked as stale. +- The tray state reflects multiple configured servers. +- Notification transitions and cooldowns have deterministic tests. +- Sleep/wake and network reconnect trigger a debounced refresh. + +## Phase 5: insights and safe fixes + +### Deterministic diagnostics + +Create pure diagnostic rules that accept a snapshot and return findings. Initial rules: + +| Rule | Warning | Critical | Persistence | +|---|---:|---:|---:| +| Disk usage | >= 75% | >= 90% | One sample | +| RAM usage | >= 80% | >= 95% | Three samples | +| CPU usage | >= 80% | >= 95% | Three samples | +| SSH latency | >= 750 ms | >= 2,000 ms | Three samples | +| Server reachability | N/A | Unreachable | Two attempts | +| App state | Stopped | Restarting/unhealthy | One sample after initial scan | +| Service state | Stopped | Restarting/unhealthy | One sample after initial scan | + +Each finding contains: + +```go +type Finding struct { + ID string `json:"id"` + Rule string `json:"rule"` + Severity string `json:"severity"` + Summary string `json:"summary"` + Evidence []Evidence `json:"evidence"` + RecommendedFixID string `json:"recommendedFixId,omitempty"` + FirstObservedAt time.Time `json:"firstObservedAt"` + LastObservedAt time.Time `json:"lastObservedAt"` +} +``` + +Do not infer a precise cause from one resource sample. Phrase findings as observations and provide the evidence used. + +### Fix safety classes + +| Class | Examples | Confirmation | +|---|---|---| +| Read only | Refresh, inspect logs, rerun diagnostics | None | +| Reversible | Start or restart an app | One confirmation, optionally remember preference | +| Availability affecting | Stop or update an app | Confirmation every time | +| Destructive | Remove, restore, firewall, database changes | Not available in first beta | + +Every state-changing action shows: + +- Target server and application. +- Exact high-level action. +- Expected availability impact. +- Start time, progress, and final result. +- A link to relevant logs after failure. + +Store a local action history without environment values, passwords, private keys, license keys, or complete unredacted logs. + +### Phase 5 acceptance criteria + +- Diagnostic rule tests cover boundary values and persistence. +- Findings show their evidence and last observation time. +- Restart/start actions require the correct confirmation. +- Duplicate clicks cannot start the same action twice concurrently. +- The app refreshes server state immediately after an action. +- Destructive operations are absent from the bridge allowlist. + +## Phase 6: packaging and release + +### Versioning + +Use independent tags: + +```text +v0.22.0 # Neo CLI release +desktop-v0.1.0 # Neo Desktop release +``` + +The desktop semantic version, bridge build version, Git commit, and protocol version are returned by `bridge.hello` and shown in About/diagnostics. + +### CI workflow + +Add `.github/workflows/desktop-ci.yml` with path filters for: + +- `apps/desktop/**` +- `cmd/neo-bridge/**` +- `internal/operations/**` +- Shared packages imported by operations. + +Required checks: + +1. Root Go unit tests. +2. Bridge protocol and contract tests. +3. Frontend lint, TypeScript check, and unit tests. +4. Rust formatting, lint, and tests. +5. macOS application build. +6. Windows x64 application build. + +Run cross-platform tray smoke tests on native GitHub runners. Do not make a cross-compiled artifact the only verification for its target OS. + +### Release workflow + +Add `.github/workflows/desktop-release.yml`, triggered by `desktop-v*` tags. + +Build in this order per target: + +1. Build the target-specific `neo-bridge` from the tag commit. +2. Place it under the Tauri sidecar filename expected for the target triple. +3. Build the frontend. +4. Build and package the Tauri application. +5. Sign the application, embedded bridge, and installer as required. +6. Generate signed updater artifacts. +7. Smoke test the installed application. +8. Publish all artifacts and checksums atomically. + +Initial production targets: + +- macOS ARM64. +- macOS Intel, or a universal package if testing confirms the updater works cleanly with it. +- Windows x64. + +Add Windows ARM64 after the first beta unless a launch customer requires it immediately. + +### Signing prerequisites + +Obtain these early; do not leave signing until the release week: + +- Apple Developer ID Application certificate. +- App Store Connect/notarization credentials. +- Windows Authenticode certificate and timestamping configuration. +- Tauri updater signing key stored only in CI secrets. + +Never place signing keys, certificate passwords, or updater private keys in this repository. + +### Update behavior + +- Check silently after startup and every six hours. +- Prompt before downloading a normal update. +- Verify the updater signature before installation. +- Show release notes and target version. +- Allow deferral, but do not repeatedly prompt during the same session. +- Treat bridge and desktop as one indivisible update; never update only the sidecar. + +### Phase 6 acceptance criteria + +- A clean Mac installs, launches, updates, and uninstalls the signed app. +- A clean Windows 10/11 machine installs, launches, updates, and uninstalls it. +- macOS Gatekeeper and Windows SmartScreen recognize signed artifacts appropriately. +- The embedded bridge is the expected version and signature. +- A bad updater signature fails closed. +- Upgrade preserves desktop preferences and existing `~/.neo/config.json`. + +## Testing strategy + +### Go tests + +- Snapshot parsers with fixture output from supported Linux distributions. +- Empty/missing Docker state. +- SSH timeout, authentication failure, unknown host, and cancellation. +- Application lifecycle validation and command quoting. +- Diagnostic thresholds and persistence. +- Protocol encoding, malformed messages, duplicate IDs, and version mismatch. +- Ensure logs and errors redact sensitive values. + +### Frontend tests + +- Healthy, warning, critical, unknown, stale, and loading states. +- No-server and not-activated onboarding. +- Server switching and manual refresh. +- Confirmation flows. +- Log batching, pause, clear, and bounded in-memory history. +- Notification transition reducer. +- Keyboard navigation and screen-reader labels. + +### Rust/Tauri tests + +- Bridge launch and handshake. +- Request correlation and cancellation. +- Unexpected bridge exit and restart budget. +- Window show/hide and single-instance behavior. +- Tray menu updates. +- Capability configuration prevents arbitrary process execution. + +### Integration tests + +Reuse the existing Neo sandbox/test infrastructure where possible: + +- Configured server appears in the desktop bridge. +- Snapshot values render correctly. +- Stop/start/restart modifies the expected sandbox container. +- Logs stream and cancel without leaking SSH sessions. +- Desktop and CLI observe the same remote state after an action. + +### Manual platform matrix + +- macOS current and previous major release, Apple Silicon. +- macOS Intel before declaring Intel support. +- Windows 11 x64. +- Windows 10 x64 while supported by the chosen Tauri/WebView2 baseline. +- Light mode, dark mode, scaled displays, multiple displays. +- Laptop sleep/wake, offline/online transition, and VPN changes. +- SSH agent key, configured key file, encrypted key, missing key, and unknown host. + +## Security requirements + +- Preserve strict `known_hosts` verification. +- Never introduce an “accept all host keys” desktop option. +- Unknown hosts require an explicit foreground trust flow or use of the existing CLI initialization flow. +- Use the OS credential store if the desktop later stores password or key-passphrase material. +- Never store credentials in localStorage or frontend state longer than required. +- Mask environment values and credentials in logs and bridge errors. +- Validate all identifiers before constructing remote commands. +- Apply timeouts and cancellation to every SSH operation. +- Restrict the Tauri content security policy and plugin capabilities. +- Do not expose generic shell execution to the webview. +- Code-sign the app, embedded bridge, installer, and updates. +- Keep a local, redacted record of user-triggered mutations. + +## Observability and support bundle + +The desktop application needs its own local diagnostics without exposing secrets. + +Record: + +- Desktop, bridge, protocol, OS, and architecture versions. +- Bridge start/stop/restart events. +- Request method, duration, and error code, but not sensitive parameters. +- Poll scheduling and cache age. +- Notification transitions. +- Update checks and signature failures. + +Add an Export Diagnostic Bundle action before public beta. The bundle should contain redacted desktop logs and version/config metadata, but exclude: + +- Private keys or their contents. +- Passwords and passphrases. +- License keys. +- Application environment values. +- Full server logs unless the user explicitly adds them after preview. + +## Development commands to add + +Add root convenience targets after scaffolding: + +```make +desktop-install: + cd apps/desktop && npm ci + +desktop-bridge: + go build -o apps/desktop/src-tauri/binaries/ ./cmd/neo-bridge + +desktop-dev: desktop-bridge + cd apps/desktop && npm run tauri dev + +desktop-test: + go test ./... + cd apps/desktop && npm test -- --run + cd apps/desktop/src-tauri && cargo test +``` + +Implement target-triple naming in a script or Task target rather than requiring developers to type it manually. The script must support macOS ARM64/Intel and Windows x64 initially. + +## Implementation slices + +Keep pull requests reviewable in this sequence: + +1. **Desktop scaffold first** + - Tauri/React project, fixture UI, tray, two windows, frontend tests, Windows compile check. +2. **Bridge skeleton** + - Sidecar packaging, `bridge.hello`, protocol client, supervision, contract tests. +3. **Servers and snapshots** + - Shared operation service, real config, typed snapshot, CLI compatibility tests. +4. **Application list and tray polling** + - Multiple servers, cache, backoff, aggregate tray state. +5. **Logs** + - Bounded recent logs, follow stream, cancellation, full-window viewer. +6. **Diagnostics and notifications** + - Deterministic rules, persistence, transition notifications. +7. **Safe lifecycle actions** + - Start/stop/restart, confirmation, action history, immediate refresh. +8. **Release engineering** + - Native CI, signing, notarization, installers, updater, smoke tests. +9. **Beta hardening** + - Sleep/wake, offline behavior, accessibility, support bundle, performance. + +Each slice must leave the CLI build and test suite green. + +## Rough schedule for one developer + +| Work | Estimate | +|---|---:| +| Scaffold and validate tray UX | 4-6 days | +| Bridge and shared operation extraction | 6-9 days | +| Live status, caching, and polling | 4-6 days | +| Logs, diagnostics, notifications, actions | 7-10 days | +| Packaging, signing, updates, hardening | 5-8 days | + +Expected first beta: approximately four to six weeks, excluding delays obtaining signing certificates. + +## Risks and mitigations + +### Tray/window differences between platforms + +**Risk:** macOS menu-bar behavior and Windows notification-area behavior are not identical. + +**Mitigation:** validate the scaffold on both platforms before extracting substantial backend code. Keep platform-specific window positioning in the thin Tauri layer. + +### Two implementation languages + +**Risk:** Rust plus Go increases contributor requirements. + +**Mitigation:** keep all Neo behavior in Go. Limit Rust to process supervision, tray/windows, commands, and events. Document the boundary and test the protocol. + +### SSH polling load + +**Risk:** frequent checks create connection load and poor battery usage. + +**Mitigation:** concurrency limits, jitter, backoff, last-known cache, visibility-aware intervals, and combined remote commands. + +### CLI behavior regressions during extraction + +**Risk:** moving logic out of Cobra commands changes existing terminal behavior or JSON. + +**Mitigation:** characterize existing output before refactoring, add compatibility tests, and keep presentation mapping in `commands/`. + +### Bridge/UI version mismatch + +**Risk:** incompatible protocol or separately updated bridge. + +**Mitigation:** bundle both from one commit, handshake before use, version the protocol, and update them as one signed application. + +### Signing delays + +**Risk:** unsigned builds trigger Gatekeeper or SmartScreen and block beta adoption. + +**Mitigation:** acquire certificates during the scaffold phase and run a signed internal release before feature completion. + +## Definition of beta complete + +The desktop beta is complete when: + +1. Signed installers are available for macOS ARM64 and Windows x64. +2. The app starts from a tray icon and can launch at login. +3. It uses the existing Neo server configuration. +4. It monitors multiple configured servers without installing a remote agent. +5. It shows live server/application status and bounded logs. +6. It provides deterministic findings with evidence. +7. It safely starts, stops, and restarts applications with confirmation. +8. It notifies on failures and recoveries without repeated noise. +9. The bridge rejects unknown methods and arbitrary command execution. +10. CLI behavior and existing tests remain compatible. +11. Signed updates work on clean macOS and Windows test machines. +12. A redacted diagnostic bundle can be exported for support. + +## First implementation task + +Start with implementation slice 1 only: scaffold `apps/desktop`, create the fixture-backed tray popover, verify the two-window lifecycle on macOS, and add a Windows compile job. Do not begin the shared Go refactor until the tray shell works on both target operating systems.